Socket
Socket
Sign inDemoInstall

light-router

Package Overview
Dependencies
1
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.211 to 0.1.0

benchmark/bech.js

2

lib/caches.js
//Dependencies
var Cache = require('./cache/')
var Cache = require('base-cache')
var Routes = require('./routes')

@@ -4,0 +4,0 @@

//Dependencies
var url = require('url');
var Routes = require('./routes')

@@ -20,3 +19,3 @@

//Split the path into paths to start indexing
var paths = pathname.split('/')
var paths = splitPath(pathname)

@@ -26,27 +25,104 @@ //Size, this will be used as an index later on

//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: handler
}
table = table.staticRoutes[pathname] //recurse into
}
//Add this to the matching table
//Else, its a dynamic route, recurse into dynamicRoutes
else {
var match = add2MatchingTable(table, size, paths, parameters)
addHandler2MatchingTable(match, handler)
}
}
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: {},
}
}
//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
//Recurse into table filtered by size
table = table[size]
//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,
})
//Recurse into table created
table = table.params[ length - 1 ]
//Check if a regexp was passed
if(typeof param.regexp !== 'undefined') {
table.regexp = new RegExp(param.regexp)
}
//Add routing scheme
table.routes = {
params: [],
bases: {}
}
//Check if this is the last iteration, if so, add the handler
if(i >= size - 1) {
table = table.routes //recurse into
table.handler = handler
}
//Else recurse into next table we will be working in
else {
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
//Pointer to final table
//The final table contains all metadata of the route
this.table = table
//Set caching on by default
this.table.cache = true
//Cache controller
this.cache = function setCache(bool) {
this.table.cache = bool
return this
}
return this
}
//Helper function to trim the pathnames first and last slash

@@ -75,6 +151,28 @@ //@return string (trimed pathname)

if(path.charAt(0) === ':') {
params.push({
pos: parseInt(p),
name: path.substring(1)
})
var push_data = {
pos: parseInt(p)
}
//Remove `:` from path string
path = path.substring(1)
//Check if an regexp was passed
if(path.indexOf('(') > -1 && path.lastIndexOf(')') > -1) {
//Start, end
var start = path.indexOf('(')
var end = path.lastIndexOf(')')
//Extract regexp string from path
var regexp = path.substring(start + 1, end)
//Remove regexp from path identifier
path = path.substring(0, start)
push_data.regexp = regexp
}
//Append parameter identifier
push_data.name = path
params.push(push_data)
}

@@ -97,80 +195,63 @@ }

//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 properly parse a url, this replaces require('url').parse(path).pathname
function urlParse(url) {
var query = url.indexOf('?')
//Helper function to add a entry to a determined matching table
//@return object
function add2MatchingTable(table, size, paths, params) {
var matching_table = table.matching
if(query !== -1) {
url = url.substring(0, query)
}
//Check if this table was already initialized
initMatchingTable(matching_table, size)
return url
}
//Generate a match entry
var entry = []
//Helper function to split the path into smaller base paths
//A simple pathname.split('/') wont work, since regexp can contain slashes
//@input string
//@return array
function splitPath(path) {
var paths = []
var buffer = ''
var i = 0
for(var i = 0; i < size; i++) {
//Defaults
var type = 0
var _static = "", param = ""
//Modes
var inParameter = false, inRegexp = false
//Index of parameter in parameters array, if exists
var index = indexOfParam(i, params)
while(i < path.length) {
//Check if a parameter is starting
if(path[i - 1] == '/' && path[i] == ':') {
inParameter = true
}
//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]
//Check if they passed a regexp
//This initialize the regexp buffer
if(inParameter === true && inRegexp === false && path[i] == '(') {
inRegexp = true
}
//Its a parameter
//Deactivate
else if(inRegexp === true && path[i] == ')' && path[i + 1] == '/') {
inRegexp = false
}
//Clear buffer or append
if(path[i] == '/' && inRegexp === false) {
//Clear buffer
paths.push(buffer)
buffer = ''
inParameter = false //default
}
else {
type = 1 //Use the parameter algorith matching
param = params[index].name
buffer = buffer + path[i]
}
//Push this position to the matching size table
entry.push({
type: type,
static: _static,
param: param
})
i++ //next character
}
//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] = []
//Push any remaining buffers
if(buffer) {
paths.push(buffer)
}
}
//Helper function to properly parse a url, this replaces require('url').parse(path).pathname
function urlParse(url) {
var query = url.indexOf('?')
if(query !== -1) {
url = url.substring(0, query)
}
return url
return paths
}
//Exports

@@ -177,0 +258,0 @@ Parser.trimPathname = trimPathname

@@ -1,2 +0,2 @@

//Dependencies
//Route parser
var Parser = require('./parser')

@@ -10,2 +10,8 @@

//Return a 404 http code
var notFound = function defaultNotFound(req, res) {
res.statusCode = 404
res.end('404 - Not found')
}
//Route finder, if nothing is matched nothing is done

@@ -20,7 +26,13 @@ //@return void

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

@@ -30,14 +42,13 @@ return

//Parse the url
var path = Parser.url(req.url)
//Try to match one of the static routes first, only then fallback to the dynamic routes
var static_match = table.staticRoutes[path]
//Remove the first and last slash frm the path if present
path = Parser.trimPathname(path)
if(typeof static_match !== 'undefined') {
var handler = static_match.handler
//Try to match one of the static routes first, only then fallback to the dynamic routes
var static_match = table.static[path]
if(static_match.cache === true) {
cache.add(path, {handler: handler})
}
if(typeof static_match !== 'undefined') {
cache.add(req.url, {handler: static_match})
static_match(req, res)
handler(req, res)
return

@@ -52,57 +63,79 @@ }

//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
continue_to_next_value:
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
continue //to next match
}
//Else, check the parameters table
else if(table.params.length > 0) {
var params = table.params
for(var p in params) {
var key = params[p].param
var rule = params[p].regexp
if(typeof rule !== 'undefined') {
if(rule.test(value) === false) {
continue //Try matching next parameter, if exists
}
}
//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
}
//If we got here, we found a possible parameter :)
req.params[key] = value
table = params[p].routes //recurse into
continue continue_to_next_value //Break to next part of the paths
}
}
//If we got here, nothing was found, return 404
//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
//But just in case my algorithm has a logic error, i'll add the default notFound :)
var routeHandler = table.handler || notFound
var shouldCache = table.cache || true
if(shouldCache === true) {
cache.add(path, {handler: routeHandler, params: req.params})
}
routeHandler(req, res)
}
//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"})
//Expose routing table
//Return the routing table
Router.routingTable = function RoutingTable() {

@@ -112,3 +145,28 @@ return Routes

//Expose the cache table, used in tests
Router.baseCache = Cache
//Overide custom 404 not found
Router.notFound = function overideDefaultNotFound(fnc) {
notFound = fnc
}
//Cache controller methods
Router.cache = {}
//Set cache max size, default 10000
Router.cache.maxSize = function setCacheMaxSize(size) {
for(var prop in Cache) {
Cache[prop].max_size = size
}
}
//Clear the cache tables
Router.cache.clear = function clearCacheTables() {
for(var prop in Cache) {
Cache[prop].clear()
}
}
//Exports
module.exports = Router

@@ -1,19 +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,
//Pseudo method, this is an route that will match all other routes
ALL_OF_THE_ABOVE: 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.211",
"description": "A light http request router for nodejs",
"version": "0.1.0",
"description": "A node.js perfomance efficient http router.",
"main": "index.js",

@@ -10,3 +10,3 @@ "directories": {

"scripts": {
"test": "node --prof ./node_modules/.bin/nodeunit test"
"test": "node ./node_modules/.bin/nodeunit test"
},

@@ -31,3 +31,6 @@ "repository": {

"nodeunit": "*"
},
"dependencies": {
"base-cache": "0.0.12"
}
}

@@ -5,13 +5,96 @@ light-router

A light node.js http request router, it doesn't use regexp for matching, thus doesn't support complex patterns. It aims for performance and a more implicit route declaration model. This module follows a singletone design pattern, see below.
A router for node.js performance junkies :)
* Note that this router is in an alpha version, I don't guarantee forward compatibility with future versions.
Why?
---------
Most node.js http routers I tested had a really high overhead for a router, and in some cases it became the bottleneck of really simple API's I wrote. [See benchmarks below](#benchmarks)
**Under development**
##TODO
- Add cache layer
- Think about adding regexp
- Add all http methods to routing table
- Remove url.parse dependencie, this thing is slow as hell!
Install
---------
```
npm install light-router
```
Sample usage
---------
```javascript
var router = require('light-router')
router.get('/v1/account/:user', function(req, res) {
res.end('Hello, ' + req.params.user)
})
```
* Note that this module has a singleton design pattern.
* The only thing that this router does to the `req` object is attach params.
Features
----------
* **Hashtable based** routing
* **Cache layer** for faster delivery
* **Cache control** for setting cache max size and disabling cache for some routes
* **RegExp** for parameter testing
* **404**, set custom not found handler
Methods overview
------------
I recommend reading everything if you want to get a general overview of the architecture of this router.
####router[method]\(route, handler)
The following http methods are avaialble: **get, post, put, head, delete, options, trace, connect**
Writing routes follows the express/rails model. You can write **:param** to extract params from the request url and **:param(regexp)** to create rules for the parameter. **Be careful with the order you declare your routes!**
```javascript
//Will only match integer userIDs
router.get('/v1/user/:id([0-9])', function(req, res) {
res.end('Your numeric userID is: ' + req.params.id)
})
//Will only match all other ids that werent numerical only
router.get('/v1/user/:id)', function(req, res) {
res.end('Your userID is: ' + req.params.id)
})
```
####route.cache(boolean)
Control the caching for this route, you should disable caching for highly dynamic routes.
```javascript
router.put('/v1/user/:id', handler).cache(false)
```
####router.notFound(handler)
Set a custom handler for the 404 not found.
```javascript
router.notFound(function(req, res) {
res.statusCode = 404
res.end('Sorry this page was not found :(')
})
```
####router.cache.maxSize(boolean)
Set the max cache table size of each http method, by default its set to **10,000**. Each http method has its own cache table, so the total cached routes you can have is: **maxSize * http_verbs**
```javascript
router.cache.maxSize(10)
```
####router.cache.clear()
Clear the cache table, maybe it could be useful :)
```javascript
router.cache.clear()
```
<a name="benchmarks"></a>Benchmarks
---------
TODO:

@@ -71,3 +71,3 @@ /**

//Dynamic routes
exports.testDynamicRoute = function(test) {
exports.testDynamicRouteSimple = function(test) {
router.get('/document/when/:date/set/:tomorrow', function(req, res) {

@@ -84,3 +84,3 @@ test.expect(2)

//Strange dynamic route
exports.testDynamicRoute2 = function(test) {
exports.testDynamicRouteStranger = function(test) {
router.get('/document/:name/:date/set/:tomorrow', function(req, res) {

@@ -97,14 +97,104 @@ test.expect(3)

//Various dynamic route
exports.testDynamicRoute3 = function(test) {
router.get('/:document/:name/:date/set/:tomorrow', function(req, res) {
test.expect(4)
//Various dynamic route w/ regexp
exports.testDynamicRouteRegExp = function(test) {
router.get('/:document/:name/:date(^05102014$)/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')
}
//Various dynamic route w/ possible complexitys
exports.testDynamicRouteComplex = function(test) {
test.expect(3)
router.get('/api/base/:id', function(req, res) {
test.equal(req.params.id, '12312', 'data param did not match')
})
router.get('/api/:name(^john$)/:id', function(req, res) {
test.equal(req.params.name, 'john', 'data param did not match')
})
router.get('/:api/:name/:id', function(req, res) {
test.equal(req.params.name, 'lucas', 'data param did not match')
test.done()
})
TestReqTo('GET', '/api/base/12312')
TestReqTo('GET', '/api/john/12312')
TestReqTo('GET', '/something/lucas/12312')
}
//Test 404
exports.testNotFound = function(test) {
router.notFound(function(req, res) {
test.done()
})
TestReqTo('GET', '/this/route/should/not/exist')
}
//Cache on and off
exports.testRouteNoCache = function(test) {
test.expect(1)
router.get('/test/cache', function(req, res) {
}).cache(false)
TestReqTo('GET', '/test/cache')
//Check if a cache entry was created
if(typeof router.baseCache.GET.table['test/cache'] === 'undefined') {
test.ok(true)
}
//Error, the cache was created
else {
test.ok(false, 'A cache entry was actually created')
}
test.done()
}
//Change cache max size
exports.testSetCacheMaxSize = function(test) {
test.expect(1)
router.cache.maxSize(100)
test.equal(router.baseCache.GET.max_size, 100, 'Set cache max size didnt seem to work')
test.done()
}
//Check if cache layer was working
exports.testCacheLayer = function(test) {
test.expect(1)
test.ok(router.baseCache.GET.size() > 0, 'Cache layer wasnt really working')
test.done()
}
//Clear cache table
exports.testClearCacheTable = function(test) {
test.expect(1)
router.cache.clear()
test.equal(router.baseCache.GET.size(), 0, 'Cache table did not clear')
test.done()
}
//Display routing table final
exports.testRoutingTable = function(test) {
var table = router.routingTable()
//console.log(JSON.stringify(table, 2, " "))
test.done()
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc