Files
deployd/lib/router.js
2012-06-06 22:00:54 -07:00

120 lines
3.0 KiB
JavaScript

var db = require('./db')
, Context = require('./context')
, escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
, debug = require('debug')('router');
/**
* A `Router` routes incoming requests to the correct resource. It also initializes and
* executes the correct methods on a resource.
*
* @param {Resource Array} resources
* @api private
*/
function Router(resources, server) {
this.resources = resources || [];
this.server = server;
}
/**
* Route requests to resources with matching root paths.
* Generate a `ctx` object and hand it to the resource, along with the `res` by calling its `resource.handle(ctx, next)` method.
* If a resource calls `next()`, move on to the next resource.
*
* If all matching resources call next(), or if the router does not find a resource, respond with `404`.
*
* @param {ServerRequest} req
* @param {ServerResponse} res
* @api public
*/
Router.prototype.route = function (req, res) {
var router = this
, server = this.server
, url = req.url
, resources = this.matchResources(url)
, i = 0
, globals = 0;
function next() {
globals++;
return function() {
globals--;
if(!globals) { nextResource() }
}
}
// global hooks
this.resources.forEach(function(resource) {
var ctx = new Context(resource, req, res, server);
// BUG - not calling back
// if(resource.handleSession) resource.handleSession(ctx, next());
})
//TODO: Handle edge case where next() is called more than once
function nextResource() {
var resource = resources[i++]
, ctx;
if (resource) {
debug('routing %s to %s', req.url, resource.settings.path);
ctx = new Context(resource, req, res, server);
process.nextTick(function () {
resource.handle(ctx, nextResource);
});
} else {
debug('404 %s', req.url);
res.statusCode = 404;
res.end("Not Found");
}
}
// only start if there are no globals remaining
if(!globals) {
nextResource();
}
};
/**
* Get resources whose base path matches the incoming URL, and order by specificness.
* (So that /foo/bar will handle a request before /foo)
*
* @param {String} url
* @param {Resource Array} matching resources
* @api private
*/
Router.prototype.matchResources = function(url) {
var router = this
, result;
if (!this.resources || !this.resources.length) return [];
result = this.resources.filter(function(d) {
return url.match(router.generateRegex(d.settings.path));
}).sort(function(a, b) {
return specificness(b) - specificness(a);
});
return result;
}
/**
* Generates a regular expression from a base path.
*
* @param {String} path
* @return {RegExp} regular expression
* @api private
*/
Router.prototype.generateRegex = function(path) {
if (!path || path === '/') path = '';
path = path.replace(escapeRegExp, '\\$&')
return new RegExp('^' + path + '(?:[/?].*)?$');
}
function specificness(resource) {
var path = resource.settings.path;
if (!path || path === '/') path = '';
return path.split('/').length;
}
module.exports = Router;