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:

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
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/")

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

Redefined classes

redef class HttpRequest

nitcorn :: vararg_routes $ HttpRequest

A request received over HTTP, is build by HttpRequestParser
redef class Route

nitcorn :: vararg_routes $ Route

A route to an Action according to a path
redef class Routes

nitcorn :: vararg_routes $ Routes

A list of routes with the search method []

All class definitions

redef class HttpRequest

nitcorn :: vararg_routes $ HttpRequest

A request received over HTTP, is build by HttpRequestParser
redef class Route

nitcorn :: vararg_routes $ Route

A route to an Action according to a path
redef class Routes

nitcorn :: vararg_routes $ Routes

A list of routes with the search method []
package_diagram nitcorn::vararg_routes vararg_routes nitcorn::server_config server_config nitcorn::vararg_routes->nitcorn::server_config nitcorn::http_request http_request nitcorn::vararg_routes->nitcorn::http_request core core nitcorn::server_config->core serialization serialization nitcorn::http_request->serialization ...core ... ...core->core ...serialization ... ...serialization->serialization nitcorn::reactor reactor nitcorn::reactor->nitcorn::vararg_routes nitcorn::file_server file_server nitcorn::file_server->nitcorn::reactor nitcorn::signal_handler signal_handler nitcorn::signal_handler->nitcorn::reactor nitcorn::log log nitcorn::log->nitcorn::reactor nitcorn::proxy proxy nitcorn::proxy->nitcorn::reactor nitcorn::file_server... ... nitcorn::file_server...->nitcorn::file_server nitcorn::signal_handler... ... nitcorn::signal_handler...->nitcorn::signal_handler nitcorn::log... ... nitcorn::log...->nitcorn::log nitcorn::proxy... ... nitcorn::proxy...->nitcorn::proxy

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module http_request

nitcorn :: http_request

Provides the HttpRequest class and services to create it
module server_config

nitcorn :: server_config

Classes and services to configure the server

Children

module reactor

nitcorn :: reactor

Core of the nitcorn project, provides HttpFactory and Action

Descendants

module a_star-m

a_star-m

module example_angular

popcorn :: example_angular

This is an example of how to use angular.js with popcorn
module file_server

nitcorn :: file_server

Provides the FileServer action, which is a standard and minimal file server
module hooks

github :: hooks

Github hook event listening with nitcorn.
module htcpcp_server

nitcorn :: htcpcp_server

A server that implements HTCPCP. At the moment there are no additions.
module loader

github :: loader

module log

nitcorn :: log

Services inserting a timestamp in all prints and to log each requests
module nitcorn

nitcorn :: nitcorn

The nitcorn Web server framework creates server-side Web apps in Nit
module nitcorn_hello_world

nitcorn :: nitcorn_hello_world

Hello World Web server example
module nitcorn_reverse_proxy

nitcorn :: nitcorn_reverse_proxy

Minimal example using a ProxyAction
module pop_auth

popcorn :: pop_auth

Authentification handlers.
module pop_handlers

popcorn :: pop_handlers

Route handlers.
module pop_json

popcorn :: pop_json

Introduce useful services for JSON REST API handlers.
module pop_routes

popcorn :: pop_routes

Internal routes representation.
module pop_sessions

popcorn :: pop_sessions

Session handlers
module pop_tasks

popcorn :: pop_tasks

Popcorn threaded tasks
module pop_templates

popcorn :: pop_templates

Template rendering for popcorn
module pop_tests

popcorn :: pop_tests

Popcorn testing services
module popcorn

popcorn :: popcorn

Application server abstraction on top of nitcorn.
module proxy

nitcorn :: proxy

Provides the ProxyAction action, which redirects requests to another interface
module pthreads

nitcorn :: pthreads

Activate the use of pthreads with nitcorn
module restful

nitcorn :: restful

Support module for the nitrestful tool and the restful annotation
module restful_annot

nitcorn :: restful_annot

Example for the restful annotation documented at lib/nitcorn/restful.nit
module signal_handler

nitcorn :: signal_handler

Handle SIGINT and SIGTERM to close the server after all active events
module simple_file_server

nitcorn :: simple_file_server

Basic file server on port 80 by default, may require 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