HTTP router based on a strict path tree structure
npm install http-hash[![build status][build-png]][build]
[![Coverage Status][cover-png]][cover]
[![Davis Dependency status][dep-png]][dep]
[![NPM][npm-png]][npm]
HTTP router based on a strict path tree structure
``js
var HttpHash = require('http-hash');
// Create a new http hash
var hash = HttpHash();
// Create a route mapping to /test/
hash.set('/test/:foo/', function (req, res) {
res.end();
});
// Get a valid route
var route = hash.get('/test/var');
console.log(route);
/*
-> {
handler: function (req, res) {},
params: {
foo: 'var'
},
splat: null
}
*/
// Get an invalid route (returns null)
var missing = hash.get('/missing');
console.log(missing);
/*
-> {
handler: null,
params: {},
splat: null
}
*/
`
`js
var HttpHash = require('http-hash');
// Create a new http hash
var hash = HttpHash();
// Create a route mapping to /foo/
hash.set('/foo/:test/*', function (req, res) {
res.end();
});
var route = hash.get('/foo/val/one/two/three');
console.log(route);
/*
-> {
handler: function (req, res) { ... },
params: {
test: 'val'
},
splat: 'one/two/three'
}
*/
`
The most popular node routers are based on regular expression
matching. This means that the order in which the routes are
defined affects the resolution of a route to handler. Sometimes
this is desirable, but it would often be better to have a
resolution scheme that is easier to reason about.
http-hash solves the routing problem by making route resolution/
independent of the order in which routes are defined. It does so
by breaking a path into a tree of nodes, based on a simple split
on . For example, the route /foo/bar/baz is treated as treefoo > bar > baz
nodes . We call foo, bar and baz path
segments.
Theses path segments are arranged into a tree of nodes, where
each segment defines a node in the tree. Each node can point to:
- a fixed handler node.handler, otherwise known as the node
value
- a set of static paths indexed by path name node.staticPaths
- a variable subtree node.variablePaths, that can match a
single named parameter OR the remainder of a route (splat).
If the last character of a defined route is *, a variable path/fs
(or splat) will be inserted, consuming the rest of the path.
This allows for subrouting, i.e. if you want to mount a static
filesystem on you would set the path as /fs/* wherefs > *
the nodes are broken down into the tree . The remainder
of the route will be returned as a "splat" value, allowing for
further routing.
In the simple case, the route tree is based on exact matches on
the name of the segment. That is to say, for the case where we
want to match /foo/bar/baz, the tree looks like
`js`
{
staticPaths: {
foo: {
staticPaths: {
bar: {
staticPaths: {
baz: {
handler: function (req, res) {}
}
}
}
}
}
}
}
When defining routes, variable paths may be specified. This is
where path segments are prefixed with : i.e. /foo/:bar/baz.
For the :bar segment, the route consumes the single variable/foo/:bar/baz looks like
route slot for that node in the tree. So for example, the route
``js`
{
staticPaths: {
foo: {
variablePaths: {
staticPaths: {
'baz': function (req, res) {}
}
}
}
}
}
Since a node can have both static and dynamic paths associated
with it, the static path will win over the variable path when we
resolve the path.
In most cases the trailing / does not matter. Variables cannot be
the empty string, and neither can splats. A splat value will not
contain the leading slash as it is consumed by the parent node.
If a path conflict occurs, an exception will be thrown. Conflicts
occur when:
- A route is defined twice, resolving to two handlers
This is the simplest case where /foo has been defined twice.
- Variable names in the path are different
Note that /foo/:vara/ and /foo/:varb/ conflict, since theyfoo.variablePaths
both resolve to , but have different param
names.
- A variable route is defined for a splat node
In the case of splats being defined at a level, no other
other variables may be specified, as we cannot distinguish
between /foo/:var and /foo/*. It is however ok to put static/foo/bar
paths on the same level, i.e. and /foo/*. In this
case, the static paths will be tried first before yielding the
splat.
`ocaml
http-hash := () => HttpHash
type HttpHash := {
get: (String: url) => RouteResult,
set: (String: path, Any: handler) => void,
_hash: RouteNode
}
type RouteNode := {
handler: Any,
fixedPaths: Object
variablePaths: RouteNode | null
}
type RouteResult := {
handler: Any | null,
params: Object
splat: String | null
}
`
http-hash exports a safe constructor function that when calledHttpHash
returns a new . get and set methods are exposed for_hash
public consumption and the underlying data structure is
exposed for private inspection/internal use.
`ocaml`
hash.set := (String: path, Any: handler) => void
Puts a path in the route table. If the path conflicts with an
existing path, an exception will be thrown.
Routes containing a that are not part of a / prefix will
also throw an exception.
A path should look like / or /foo or /:foo or a union of/*
theses things, or optionally end with
- param names should not be repeated as they will conflict but
there is no strong assertion for this. The last param name
wins.
- specifying a variable twice for a node will cause an exception
- repeated and trailing '/' are ignored
- paths are case sensitive
- variables and splats are not matched by the empty string.
`ocaml`
hash.get := (String: path) => RouteResult
Gets a route from the route table. If there is no viable route,
the handler will be returned as null in the RouteResult
object.
The route result contains a params hash, containing a key forsplat
each named variable in the path. Additionally, if a splat route
was defined, the property will contain the tail portion
of the route matched.
npm install http-hash
npm test`
- Matt Esch
[build-png]: https://secure.travis-ci.org/Matt-Esch/http-hash.png
[build]: https://travis-ci.org/Matt-Esch/http-hash
[cover-png]: https://coveralls.io/repos/Matt-Esch/http-hash/badge.png?branch=master
[cover]: https://coveralls.io/r/Matt-Esch/http-hash
[dep-png]: https://david-dm.org/Matt-Esch/http-hash.png
[dep]: https://david-dm.org/Matt-Esch/http-hash
[test-png]: https://ci.testling.com/Matt-Esch/http-hash.png
[test]: https://ci.testling.com/Matt-Esch/http-hash
[npm-png]: https://nodei.co/npm/http-hash.png?stars&downloads
[npm]: https://nodei.co/npm/http-hash