Using vararg_routes, a Route path can contain variable parts
that will be matched against a HttpRequest URL.
Variable parameters of a route path can be specified using the : prefix:
var iface = "http://localhost:3000"
var vh = new VirtualHost(iface)
vh.routes.add new Route("/blog/articles/:articleId", new BlogArticleAction)Route arguments can be accessed from the HttpRequest within a nitcorn Action:
class BlogArticleAction
    super Action
    redef fun answer(request, url) do
        var param = request.param("articleId")
        if param == null then
            return new HttpResponse(400)
        end
        print url # let's say "/blog/articles/12"
        print param # 12
        return new HttpResponse(200)
    end
endRoute can match variables expression.
# We need an Action to try routes.
class DummyAction super Action end
var action = new DummyAction
var route = new Route("/users/:id", action)
assert not route.match("/users")
assert route.match("/users/1234")
assert route.match("/users/")Route without uri parameters still behave like before.
route = new Route("/users", action)
assert route.match("/users")
assert route.match("/users/1234")
assert not route.match("/issues/1234")Priority depends on the order the routes were added to the Routes dispatcher.
var host = new VirtualHost("")
var routes = new Routes(host)
routes.add new Route("/:a/:b/:c", action)
routes.add new Route("/users/:id", action)
routes.add new Route("/:foo", action)
assert routes["/a/b/c"].path == "/:a/:b/:c"
assert routes["/a/b/c/d"].path == "/:a/:b/:c"
assert routes["/users/1234/foo"].path == "/:a/:b/:c"
assert routes["/users/"].path == "/users/:id"
assert routes["users/"].path == "/users/:id"
assert routes["/users/1234"].path == "/users/:id"
assert routes["/users"].path == "/:foo"
assert routes["/"].path == "/:foo"
assert routes[""].path == "/:foo"Parameters can be accessed by parsing the uri.
route = new Route("/users/:id", action)
var params = route.parse_params("/users/1234")
assert params.has_key("id")
assert not params.has_key("foo")
assert params["id"] == "1234"Or from the HttpRequest.
route = new Route("/users/:id", action)
var req = new HttpRequest
req.uri_params = route.parse_params("/users/1234")
assert req.params == ["id"]
assert req.param("id") == "1234"
assert req.param("foo") == nullnitcorn :: vararg_routes $ HttpRequest
A request received over HTTP, is build byHttpRequestParser
			nitcorn :: vararg_routes $ HttpRequest
A request received over HTTP, is build byHttpRequestParser
			Serializable::inspect to show more useful information
			serialization :: serialization_core
Abstract services to serialize Nit objects to different formatscore :: union_find
union–find algorithm using an efficient disjoint-set data structureHttpRequest class and services to create it
			nitc :: api_metrics
FileServer action, which is a standard and minimal file server
			restful annotation documented at lib/nitcorn/restful.nit
			root to execute
			
# Routes with parameters.
#
# Using `vararg_routes`, a `Route` path can contain variable parts
# that will be matched against a `HttpRequest` URL.
#
# Variable parameters of a route path can be specified using the `:` prefix:
#
# ~~~nitish
# var iface = "http://localhost:3000"
# var vh = new VirtualHost(iface)
# vh.routes.add new Route("/blog/articles/:articleId", new BlogArticleAction)
# ~~~
#
# Route arguments can be accessed from the `HttpRequest` within a nitcorn `Action`:
#
# ~~~nitish
# class BlogArticleAction
#	super Action
#
#	redef fun answer(request, url) do
#		var param = request.param("articleId")
#		if param == null then
#			return new HttpResponse(400)
#		end
#
#		print url # let's say "/blog/articles/12"
#		print param # 12
#
#		return new HttpResponse(200)
#	end
# end
# ~~~
#
# ## Route matching
#
# Route can match variables expression.
#
# ~~~
# # We need an Action to try routes.
# class DummyAction super Action end
# var action = new DummyAction
#
# var route = new Route("/users/:id", action)
# assert not route.match("/users")
# assert route.match("/users/1234")
# assert route.match("/users/") # empty id
# ~~~
#
# Route without uri parameters still behave like before.
#
# ~~~
# route = new Route("/users", action)
# assert route.match("/users")
# assert route.match("/users/1234")
# assert not route.match("/issues/1234")
# ~~~
#
# ## Route priority
#
# Priority depends on the order the routes were added to the `Routes` dispatcher.
#
# ~~~
# var host = new VirtualHost("")
# var routes = new Routes(host)
#
# routes.add new Route("/:a/:b/:c", action)
# routes.add new Route("/users/:id", action)
# routes.add new Route("/:foo", action)
#
# assert routes["/a/b/c"].path == "/:a/:b/:c"
# assert routes["/a/b/c/d"].path == "/:a/:b/:c"
# assert routes["/users/1234/foo"].path == "/:a/:b/:c"
#
# assert routes["/users/"].path == "/users/:id"
# assert routes["users/"].path == "/users/:id"
# assert routes["/users/1234"].path == "/users/:id"
#
# assert routes["/users"].path == "/:foo"
# assert routes["/"].path == "/:foo"
# assert routes[""].path == "/:foo"
# ~~~
#
# ## Accessing uri parameter and values
#
# Parameters can be accessed by parsing the uri.
#
# ~~~
# route = new Route("/users/:id", action)
# var params = route.parse_params("/users/1234")
# assert params.has_key("id")
# assert not params.has_key("foo")
# assert params["id"] == "1234"
# ~~~
#
# Or from the `HttpRequest`.
#
# ~~~
# route = new Route("/users/:id", action)
# var req = new HttpRequest
# req.uri_params = route.parse_params("/users/1234")
# assert req.params == ["id"]
# assert req.param("id") == "1234"
# assert req.param("foo") == null
# ~~~
module vararg_routes
import server_config
import http_request
# A route to an `Action` according to a `path`
redef class Route
	redef init do
		super
		parse_pattern(path)
	end
	# Replace `self.path` parameters with concrete values from the `request` URI.
	fun resolve_path(request: HttpRequest): nullable String do
		if pattern_parts.is_empty then return self.path
		var path = "/"
		for part in pattern_parts do
			if part isa UriString then
				path /= part.string
			else if part isa UriParam then
				path /= request.param(part.name) or else part.name
			end
		end
		return path
	end
	# Cut `path` into `UriParts`.
	private fun parse_pattern(path: nullable String) do
		if path == null then return
		path = standardize_path(path)
		var parts = path.split("/")
		for part in parts do
			if not part.is_empty and part.first == ':' then
				# is an uri param
				var name = part.substring(1, part.length)
				var param = new UriParam(name)
				pattern_parts.add param
			else
				# is a standard string
				pattern_parts.add new UriString(part)
			end
		end
	end
	# `UriPart` forming `self` pattern.
	private var pattern_parts = new Array[UriPart]
	# Does `self` matches `uri`?
	fun match(uri: nullable String): Bool do
		if pattern_parts.is_empty then return true
		if uri == null then return false
		uri = standardize_path(uri)
		var parts = uri.split("/")
		for i in [0 .. pattern_parts.length[ do
			if i >= parts.length then return false
			var ppart = pattern_parts[i]
			var part = parts[i]
			if not ppart.match(part) then return false
		end
		return true
	end
	# Extract parameter values from `uri`.
	fun parse_params(uri: nullable String): Map[String, String] do
		var res = new HashMap[String, String]
		if pattern_parts.is_empty then return res
		if uri == null then return res
		uri = standardize_path(uri)
		var parts = uri.split("/")
		for i in [0 .. pattern_parts.length[ do
			if i >= parts.length then return res
			var ppart = pattern_parts[i]
			var part = parts[i]
			if not ppart.match(part) then return res
			if ppart isa UriParam then
				res[ppart.name] = part
			end
		end
		return res
	end
	# Remove first occurence of `/`.
	private fun standardize_path(path: String): String do
		if not path.is_empty and path.first == '/' then
			return path.substring(1, path.length)
		end
		return path
	end
end
# A String that compose an URI.
#
# In practice, UriPart can be parameters or static strings.
private interface UriPart
	# Does `self` matches a part of the uri?
	fun match(uri_part: String): Bool is abstract
end
# An uri parameter string like `:id`.
private class UriParam
	super UriPart
	# Param `name` in the route uri.
	var name: String
	# Parameters match everything.
	redef fun match(part) do return true
	redef fun to_s do return name
end
# A static uri string like `users`.
private class UriString
	super UriPart
	# Uri part string.
	var string: String
	# Empty strings match everything otherwise matching is based on string equality.
	redef fun match(part) do return string.is_empty or string == part
	redef fun to_s do return string
end
redef class Routes
	# Use `Route::match` instead of `==`.
	redef fun [](key) do
		for route in routes do
			if route.match(key) then return route
		end
		return null
	end
end
redef class HttpRequest
	# Parameters found in uri associated to their values.
	var uri_params: Map[String, String] = new HashMap[String, String] is public writable
	# Get the value for parameter `name` or `null`.
	fun param(name: String): nullable String do
		if not uri_params.has_key(name) then return null
		return uri_params[name]
	end
	# List all uri parameters matched by this request.
	fun params: Array[String] do return uri_params.keys.to_a
end
lib/nitcorn/vararg_routes.nit:17,1--269,3