mirror of
https://github.com/zhigang1992/deployd.git
synced 2026-01-13 09:00:43 +08:00
141 lines
3.7 KiB
JavaScript
141 lines
3.7 KiB
JavaScript
var db = require('./db')
|
|
, Context = require('./context')
|
|
, escapeRegExp = /[\-\[\]{}()+?.,\\\^$|#\s]/g
|
|
, debug = require('debug')('router')
|
|
, doh = require('doh')
|
|
, error404 = doh.createResponder()
|
|
, async = require('async');
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if (req._routed) {
|
|
return;
|
|
}
|
|
|
|
req._routed = true;
|
|
|
|
async.series([function(fn) {
|
|
async.forEach(router.resources, function(resource, fn) {
|
|
if(resource.handleSession) {
|
|
var ctx = new Context(resource, req, res, server);
|
|
resource.handleSession(ctx, fn);
|
|
} else {
|
|
fn();
|
|
}
|
|
}, fn);
|
|
}], function(err) {
|
|
if (err) throw err;
|
|
nextResource();
|
|
});
|
|
|
|
//TODO: Handle edge case where ctx.next() is called more than once
|
|
function nextResource() {
|
|
var resource = resources[i++]
|
|
, ctx;
|
|
|
|
var handler = doh.createHandler({req: req, res: res, server: server});
|
|
handler.run(function () {
|
|
process.nextTick(function () {
|
|
if (resource) {
|
|
debug('routing %s to %s', req.url, resource.path);
|
|
ctx = new Context(resource, req, res, server);
|
|
ctx.router = router;
|
|
|
|
// default root to false
|
|
if(ctx.session) ctx.session.isRoot = req.isRoot || false;
|
|
|
|
// external functions
|
|
var furl = ctx.url.replace('/', '');
|
|
if(resource.external && resource.external[furl]) {
|
|
resource.external[furl](ctx.body, ctx, ctx.done);
|
|
} else {
|
|
resource.handle(ctx, nextResource);
|
|
}
|
|
} else {
|
|
debug('404 %s', req.url);
|
|
res.statusCode = 404;
|
|
error404({message: 'resource not found'}, req, res);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* 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;
|
|
|
|
debug('resources %j', this.resources.map(function(r) { return r.path; }));
|
|
|
|
if (!this.resources || !this.resources.length) return [];
|
|
|
|
result = this.resources.filter(function(d) {
|
|
return url.match(router.generateRegex(d.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.path;
|
|
if (!path || path === '/') path = '';
|
|
return path.split('/').length;
|
|
}
|
|
|
|
module.exports = Router; |