Rinser/node_modules/express/lib/router/index.js

337 lines
7.2 KiB
JavaScript
Raw Normal View History

2015-07-20 13:42:07 +00:00
/**
* Module dependencies.
*/
var Route = require('./route');
2015-08-14 19:58:05 +00:00
var utils = require('../utils');
2015-07-20 13:42:07 +00:00
var methods = require('methods');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
/**
2015-08-14 19:58:05 +00:00
* Expose `Router` constructor.
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
exports = module.exports = Router;
2015-07-20 13:42:07 +00:00
/**
* Initialize a new `Router` with the given `options`.
*
* @param {Object} options
2015-08-14 19:58:05 +00:00
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
function Router(options) {
options = options || {};
var self = this;
this.map = {};
this.params = {};
this._params = [];
this.caseSensitive = options.caseSensitive;
this.strict = options.strict;
this.middleware = function router(req, res, next){
self._dispatch(req, res, next);
};
}
2015-07-20 13:42:07 +00:00
/**
2015-08-14 19:58:05 +00:00
* Register a param callback `fn` for the given `name`.
2015-07-20 13:42:07 +00:00
*
2015-08-14 19:58:05 +00:00
* @param {String|Function} name
2015-07-20 13:42:07 +00:00
* @param {Function} fn
2015-08-14 19:58:05 +00:00
* @return {Router} for chaining
* @api public
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype.param = function(name, fn){
2015-07-20 13:42:07 +00:00
// param logic
2015-08-14 19:58:05 +00:00
if ('function' == typeof name) {
2015-07-20 13:42:07 +00:00
this._params.push(name);
return;
}
// apply param functions
2015-08-14 19:58:05 +00:00
var params = this._params
, len = params.length
, ret;
2015-07-20 13:42:07 +00:00
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
(this.params[name] = this.params[name] || []).push(fn);
return this;
};
/**
2015-08-14 19:58:05 +00:00
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype._dispatch = function(req, res, next){
var params = this.params
, self = this;
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// route dispatch
(function pass(i, err){
var paramCallbacks
, paramIndex = 0
, paramVal
, route
, keys
, key;
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// match next route
function nextRoute(err) {
pass(req._route_index + 1, err);
2015-07-20 13:42:07 +00:00
}
2015-08-14 19:58:05 +00:00
// match route
req.route = route = self.matchRequest(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res, next);
// no route
if (!route) return next(err);
debug('matched %s %s', route.method, route.path);
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
// param callbacks
function param(err) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if ('route' == err) {
nextRoute();
} else if (err) {
i = 0;
callbacks(err);
} else if (paramCallbacks && undefined !== paramVal) {
paramCallback();
} else if (key) {
param();
} else {
i = 0;
callbacks();
}
} catch (err) {
param(err);
2015-07-20 13:42:07 +00:00
}
2015-08-14 19:58:05 +00:00
};
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
param(err);
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
2015-07-20 13:42:07 +00:00
}
2015-08-14 19:58:05 +00:00
// invoke route callbacks
function callbacks(err) {
var fn = route.callbacks[i++];
try {
if ('route' == err) {
nextRoute();
} else if (err && fn) {
if (fn.length < 4) return callbacks(err);
fn(err, req, res, callbacks);
} else if (fn) {
if (fn.length < 4) return fn(req, res, callbacks);
callbacks();
} else {
nextRoute(err);
}
} catch (err) {
callbacks(err);
2015-07-20 13:42:07 +00:00
}
}
2015-08-14 19:58:05 +00:00
})(0);
2015-07-20 13:42:07 +00:00
};
/**
2015-08-14 19:58:05 +00:00
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype._options = function(req, res, next){
var path = parseUrl(req).pathname
, body = this._optionsFor(path).join(',');
if (!body) return next();
res.set('Allow', body).send(body);
};
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @return {Array}
* @api private
*/
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
Router.prototype._optionsFor = function _optionsFor(path) {
var options = [];
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
if (method === 'options') continue;
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
var routes = this.map[method];
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// HEAD methods include GET routes
if (!routes && method === 'head') {
routes = this.map.get;
2015-07-20 13:42:07 +00:00
}
2015-08-14 19:58:05 +00:00
if (!routes) continue;
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
for (var j = 0; j < routes.length; j++) {
if (routes[j].match(path)) {
options.push(method.toUpperCase());
break;
}
2015-07-20 13:42:07 +00:00
}
}
2015-08-14 19:58:05 +00:00
return options.sort();
2015-07-20 13:42:07 +00:00
};
/**
2015-08-14 19:58:05 +00:00
* Attempt to match a route for `req`
* with optional starting index of `i`
* defaulting to 0.
2015-07-20 13:42:07 +00:00
*
2015-08-14 19:58:05 +00:00
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype.matchRequest = function(req, i, head){
var method = req.method.toLowerCase()
, url = parseUrl(req)
, path = url.pathname
, routes = this.map
, i = i || 0
, route;
// HEAD support
if (!head && 'head' == method) {
route = this.matchRequest(req, i, true);
if (route) return route;
method = 'get';
2015-07-20 13:42:07 +00:00
}
2015-08-14 19:58:05 +00:00
// routes for this method
if (routes = routes[method]) {
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// matching routes
for (var len = routes.length; i < len; ++i) {
route = routes[i];
if (route.match(path)) {
req._route_index = i;
return route;
}
2015-07-20 13:42:07 +00:00
}
}
};
/**
2015-08-14 19:58:05 +00:00
* Attempt to match a route for `method`
* and `url` with optional starting
* index of `i` defaulting to 0.
2015-07-20 13:42:07 +00:00
*
2015-08-14 19:58:05 +00:00
* @param {String} method
* @param {String} url
* @param {Number} i
2015-07-20 13:42:07 +00:00
* @return {Route}
2015-08-14 19:58:05 +00:00
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype.match = function(method, url, i, head){
var req = { method: method, url: url };
return this.matchRequest(req, i, head);
2015-07-20 13:42:07 +00:00
};
/**
2015-08-14 19:58:05 +00:00
* Route `method`, `path`, and one or more callbacks.
2015-07-20 13:42:07 +00:00
*
2015-08-14 19:58:05 +00:00
* @param {String} method
* @param {String} path
* @param {Function} callback...
* @return {Router} for chaining
* @api private
2015-07-20 13:42:07 +00:00
*/
2015-08-14 19:58:05 +00:00
Router.prototype.route = function(method, path, callbacks){
var method = method.toLowerCase()
, callbacks = utils.flatten([].slice.call(arguments, 2));
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// ensure path was given
if (!path) throw new Error('Router#' + method + '() requires a path');
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// ensure all callbacks are functions
callbacks.forEach(function(fn){
if ('function' == typeof fn) return;
var type = {}.toString.call(fn);
var msg = '.' + method + '() requires callback functions but got a ' + type;
throw new Error(msg);
});
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// create the route
debug('defined %s %s', method, path);
var route = new Route(method, path, callbacks, {
sensitive: this.caseSensitive,
strict: this.strict
});
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
// add it
(this.map[method] = this.map[method] || []).push(route);
return this;
};
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
Router.prototype.all = function(path) {
var self = this;
var args = [].slice.call(arguments);
methods.forEach(function(method){
self.route.apply(self, [method].concat(args));
});
return this;
};
2015-07-20 13:42:07 +00:00
2015-08-14 19:58:05 +00:00
methods.forEach(function(method){
Router.prototype[method] = function(path){
var args = [method].concat([].slice.call(arguments));
this.route.apply(this, args);
return this;
2015-07-20 13:42:07 +00:00
};
2015-08-14 19:58:05 +00:00
});