light-router
Advanced tools
Comparing version 0.0.22 to 0.0.31
@@ -51,4 +51,3 @@ var Benchmark = require('benchmark'); | ||
}) | ||
// run async | ||
.run({ 'async': false }); |
@@ -51,13 +51,17 @@ var tests = require('./test/test-router.js') | ||
start = new Date().getTime(); | ||
// | ||
while(i < samples) { | ||
benchmark_control(); | ||
i++ | ||
} | ||
start = new Date().getTime(); | ||
end = new Date().getTime(); | ||
while(i < samples) { | ||
benchmark_control(); | ||
i++ | ||
} | ||
console.log('Control benchmark, %dms', end - start) | ||
end = new Date().getTime(); | ||
console.log('Control benchmark, %dms', end - start) | ||
// | ||
start = new Date().getTime(); | ||
@@ -75,14 +79,20 @@ | ||
start = new Date().getTime(); | ||
// | ||
i = 0 | ||
while(i < samples) { | ||
benchmark_dynamic(); | ||
i++ | ||
} | ||
start = new Date().getTime(); | ||
end = new Date().getTime(); | ||
i = 0 | ||
while(i < samples) { | ||
benchmark_dynamic(); | ||
i++ | ||
} | ||
console.log('Time to route dynamic paths %d samples, %dms', samples, end - start) | ||
end = new Date().getTime(); | ||
console.log('Time to route dynamic paths %d samples, %dms', samples, end - start) | ||
// | ||
start = new Date().getTime(); | ||
i = 0 | ||
@@ -89,0 +99,0 @@ while(i < samples) { |
//Dependencies | ||
var url = require('url'); | ||
var Routes = require('./routes') | ||
@@ -25,27 +24,78 @@ | ||
//Discover parameters in the route | ||
var parameters = pathname2ParamsArray(paths) | ||
//Check if its a static route | ||
var params = pathname2ParamsArray(paths) | ||
//Static asset, add it to the static table inside the routing table | ||
if(parameters.length === 0) { | ||
addStatic2Table(table, pathname, handler) | ||
//Its static, add to static routes and finish | ||
if(params.length === 0) { | ||
table.staticRoutes[pathname] = handler | ||
return | ||
} | ||
//Add this to the matching table | ||
else { | ||
var match = add2MatchingTable(table, size, paths, parameters) | ||
addHandler2MatchingTable(match, handler) | ||
//Its a dynamic route, recurse into dynamicRoutes | ||
table = table.dynamicRoutes | ||
//Check if a routing table for the given size filter already exists, if not create | ||
if(typeof table[size] === 'undefined') { | ||
table[size] = { | ||
params: [], | ||
bases: {}, | ||
} | ||
} | ||
} | ||
//Recurse into table filtered by size | ||
table = table[size] | ||
//Helper function to check if this base_path exists in an array of routes | ||
function indexOfInRouter(base, router) { | ||
for(var i in router) { | ||
if(router[i].base === base) { | ||
return i | ||
//Add route to dynamic table table | ||
for(var i = 0; i < size; i++) { | ||
var path = paths[i] | ||
var param_index = indexOfParam(i, params) | ||
//Its an parameter | ||
if(param_index > -1) { | ||
var param = params[ param_index ] | ||
var length = table.params.push({ | ||
param: param.name, | ||
rule: null, | ||
}) | ||
//Recurse into table created | ||
table = table.params[ length - 1 ] | ||
//Add routing scheme | ||
table.routes = { | ||
params: [], | ||
bases: {} | ||
} | ||
//Check if this is the last iteration, if so, add the handler | ||
if(i >= size - 1) { | ||
table.routes.handler = handler | ||
} | ||
//Recurse into | ||
table = table.routes | ||
} | ||
//Its a base path | ||
else { | ||
//Create a new table, if not exists | ||
if(typeof table.bases[path] === 'undefined') { | ||
table.bases[path] = { | ||
params: [], | ||
bases: {}, | ||
} | ||
} | ||
//Recurse into routing table | ||
table = table.bases[path] | ||
//Check if this is the last iteration, if so, add the handler | ||
if(i >= size - 1) { | ||
table.handler = handler | ||
} | ||
} | ||
} | ||
return -1 | ||
} | ||
//Helper function to trim the pathnames first and last slash | ||
@@ -95,68 +145,2 @@ //@return string (trimed pathname) | ||
//Helper function to add a static route to a givens table static routes | ||
//@return void | ||
function addStatic2Table(table, path, handler) { | ||
table.static[path] = handler | ||
} | ||
//Helper function to add a entry to a determined matching table | ||
//@return object | ||
function add2MatchingTable(table, size, paths, params) { | ||
var matching_table = table.matching | ||
//Check if this table was already initialized | ||
initMatchingTable(matching_table, size) | ||
//Generate a match entry | ||
var entry = [] | ||
for(var i = 0; i < size; i++) { | ||
//Defaults | ||
var type = 0 | ||
var _static = "", param = "" | ||
//Index of parameter in parameters array, if exists | ||
var index = indexOfParam(i, params) | ||
//No parameter found in the given position, so its a static part of the path | ||
if(index === -1) { | ||
type = 0 //Use the static algorith matching | ||
_static = paths[i] | ||
} | ||
//Its a parameter | ||
else { | ||
type = 1 //Use the parameter algorith matching | ||
param = params[index].name | ||
} | ||
//Push this position to the matching size table | ||
entry.push({ | ||
type: type, | ||
static: _static, | ||
param: param | ||
}) | ||
} | ||
//Push the entry to the given size matching_table | ||
var length = matching_table[size].push(entry) | ||
return matching_table[size][length - 1] | ||
} | ||
//Helper function to asign a handler to the final condition of a match | ||
//@return void | ||
function addHandler2MatchingTable(match, handler) { | ||
var last = match.length - 1; | ||
match[last].handler = handler | ||
} | ||
//Helper function to initialize a matching table given its size | ||
//@return void | ||
function initMatchingTable(matching_table, size) { | ||
if(typeof matching_table[size] === 'undefined') { | ||
matching_table[size] = [] | ||
} | ||
} | ||
//Helper function to properly parse a url, this replaces require('url').parse(path).pathname | ||
@@ -163,0 +147,0 @@ function urlParse(url) { |
@@ -19,7 +19,13 @@ //Dependencies | ||
//Parse the url | ||
var path = Parser.url(req.url) | ||
//Remove the first and last slash frm the path if present | ||
path = Parser.trimPathname(path) | ||
//Try to make a cache hit here | ||
var hit = cache.find(req.url) | ||
var hit = cache.find(path) | ||
if(typeof hit !== 'undefined') { | ||
req.params = hit.params || null | ||
req.params = hit.params || {} | ||
hit.handler(req, res) | ||
@@ -29,13 +35,7 @@ return | ||
//Parse the url | ||
var path = Parser.url(req.url) | ||
//Remove the first and last slash frm the path if present | ||
path = Parser.trimPathname(path) | ||
//Try to match one of the static routes first, only then fallback to the dynamic routes | ||
var static_match = table.static[path] | ||
var static_match = table.staticRoutes[path] | ||
if(typeof static_match !== 'undefined') { | ||
cache.add(req.url, {handler: static_match}) | ||
cache.add(path, {handler: static_match}) | ||
static_match(req, res) | ||
@@ -51,55 +51,74 @@ return | ||
//Matching/parameter router based on the size | ||
var matching = table.matching[size] | ||
//Recurse into dynamicRoutes table | ||
table = table.dynamicRoutes[size] | ||
//Check if there are any dynamic routes this size, if not, dont even bother | ||
if(typeof matching !== 'undefined') { | ||
//Itenerate throught the matches of the current table we are in based in the size of paths | ||
for(var m in matching) { | ||
var match = matching[m] | ||
if(typeof table === 'undefined') { | ||
return notFound(req, res) | ||
} | ||
//Clear the request parameters holder | ||
req.params = {} | ||
//Clear the request parameters holder | ||
req.params = {} | ||
//Loop through all parts of this match | ||
current_match: | ||
for(var p in match) { | ||
var condition = match[p] | ||
//Start iterating throught the routing tree | ||
for(var i = 0; i < size; i++) { | ||
//Vars | ||
var value = paths[i] //The part of the path we are currently working with | ||
//How to match this part of the path | ||
switch(condition.type) { | ||
//Must equal to this | ||
case 0: | ||
//Check if doesnt match this condition | ||
if(condition.static !== paths[p]) { | ||
break current_match | ||
} | ||
break; | ||
//Can equal anything, just save its value | ||
case 1: | ||
//This isnt really a condition, anything will match here :) | ||
req.params[condition.param] = paths[p] | ||
//First try matching with the hashtable with base paths | ||
var base = table.bases[value] | ||
//Check if some base path was matched | ||
if(typeof base !== 'undefined') { | ||
table = base //recurse into | ||
} | ||
//Else, check the parameters table | ||
else if(table.params.length > 0) { | ||
var params = table.params | ||
for(var p in params) { | ||
var rule = params[p].rule | ||
var key = params[p].param | ||
if(rule) { | ||
//TODO: Add regexp matching | ||
} | ||
//We got a handler, run it :) it must be the last part of the path | ||
if(typeof condition.handler === 'function') { | ||
cache.add(req.url, { | ||
handler: condition.handler, | ||
params: req.params | ||
}) | ||
condition.handler(req, res) | ||
return | ||
} | ||
req.params[key] = value | ||
table = params[p].routes //recurse into | ||
} | ||
} | ||
//Else, nothing was found, return 404 | ||
else { | ||
//Add to cache, this 404, to save cpu in future requests. | ||
//We dont need to cache other 404 cause they dont take so much cpu as this one did | ||
cache.add(path, {handler: notFound}) | ||
return notFound(req, res) | ||
} | ||
} | ||
//If we are here, the dynamicRoutes algorithm must have found a route | ||
var handler = table.handler || notFound | ||
cache.add(path, {handler: handler, params: req.params}) | ||
handler(req, res) | ||
} | ||
//Return a 404 http code | ||
function notFound(req, res) { | ||
res.statusCode = 404 | ||
res.end('404 - Not found') | ||
} | ||
//Router methods | ||
Router.get = Parser.bind({method: "GET"}) | ||
Router.post = Parser.bind({method: "POST"}) | ||
Router.put = Parser.bind({method: "PUT"}) | ||
Router.head = Parser.bind({method: "HEAD"}) | ||
Router.delete = Parser.bind({method: "DELETE"}) | ||
Router.get = Parser.bind({method: "GET"}) | ||
Router.post = Parser.bind({method: "POST"}) | ||
Router.put = Parser.bind({method: "PUT"}) | ||
Router.head = Parser.bind({method: "HEAD"}) | ||
Router.delete = Parser.bind({method: "DELETE"}) | ||
Router.options = Parser.bind({method: "OPTIONS"}) | ||
Router.trace = Parser.bind({method: "TRACE"}) | ||
Router.connect = Parser.bind({method: "CONNECT"}) | ||
@@ -111,3 +130,6 @@ //Expose routing table | ||
//Expose the base-cache | ||
Router.baseCache = Cache | ||
//Exports | ||
module.exports = Router |
@@ -1,20 +0,16 @@ | ||
//Routing table struct | ||
var Routing_Table = { | ||
matching: {}, //indexed by size of paths | ||
static: {}, //indexed by full path | ||
} | ||
//Methods | ||
var Methods = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE', 'CONNECT'] | ||
//Methods table | ||
var Methods_Table = { | ||
GET: Routing_Table, | ||
POST: Routing_Table, | ||
HEAD: Routing_Table, | ||
PUT: Routing_Table, | ||
DELETE: Routing_Table, | ||
OPTIONS: Routing_Table, | ||
TRACE: Routing_Table, | ||
CONNECT: Routing_Table, | ||
//Routing hashtable | ||
var Table = {} | ||
//Build the table | ||
for(var i in Methods) { | ||
Table[ Methods[i] ] = { | ||
staticRoutes: {}, | ||
dynamicRoutes: {}, | ||
} | ||
} | ||
//Exports final table | ||
module.exports = Methods_Table; | ||
//Export it | ||
module.exports = Table |
{ | ||
"name": "light-router", | ||
"version": "0.0.22", | ||
"version": "0.0.31", | ||
"description": "A light http request router for nodejs", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -10,8 +10,17 @@ light-router | ||
**Under development** | ||
- Under development | ||
---------- | ||
##TODO | ||
- ~~Add cache layer~~ | ||
- Think about adding regexp | ||
- ~~Think about adding regexp~~ | ||
- ~~Add all http methods to routing table~~ | ||
- ~~Remove url.parse dependencie, this thing is slow as hell!~~ | ||
**Tomorrow** | ||
- ~~Implement a faster routing table for dynamic matches~~ | ||
- Write documentation and benchmarks | ||
- Release alpha version | ||
- Add optional custom 404 handler | ||
- Add caching options |
@@ -97,12 +97,20 @@ /** | ||
exports.testDynamicRoute3 = function(test) { | ||
router.get('/:document/:name/:date/set/:tomorrow', function(req, res) { | ||
test.expect(4) | ||
router.get('/:document/:name/:date/set', function(req, res) { | ||
test.expect(3) | ||
test.equal(req.params.document, 'shala', 'data param did not match') | ||
test.equal(req.params.name, 'something', 'data param did not match') | ||
test.equal(req.params.date, '05102014', 'data param did not match') | ||
test.equal(req.params.tomorrow, '06102014', 'data param did not match') | ||
test.done() | ||
}) | ||
TestReqTo('GET', '/shala/something/05102014/set/06102014') | ||
TestReqTo('GET', '/shala/something/05102014/set') | ||
} | ||
//Display routing table final | ||
exports.testRoutingTable = function(test) { | ||
var table = router.routingTable() | ||
//console.log(JSON.stringify(table, 2, " ")) | ||
test.done() | ||
} |
20856
17
580
26