6 var Route = require('./route');
7 var Layer = require('./layer');
8 var methods = require('methods');
9 var mixin = require('utils-merge');
10 var debug = require('debug')('express:router');
11 var deprecate = require('depd')('express');
12 var parseUrl = require('parseurl');
13 var utils = require('../utils');
19 var objectRegExp = /^\[object (\S+)\]$/;
20 var slice = Array.prototype.slice;
21 var toString = Object.prototype.toString;
24 * Initialize a new `Router` with the given `options`.
26 * @param {Object} options
27 * @return {Router} which is an callable function
31 var proto = module.exports = function(options) {
32 options = options || {};
34 function router(req, res, next) {
35 router.handle(req, res, next);
38 // mixin Router class functions
39 router.__proto__ = proto;
43 router.caseSensitive = options.caseSensitive;
44 router.mergeParams = options.mergeParams;
45 router.strict = options.strict;
52 * Map the given param placeholder `name`(s) to the given callback.
54 * Parameter mapping is used to provide pre-conditions to routes
55 * which use normalized placeholders. For example a _:user_id_ parameter
56 * could automatically load a user's information from the database without
57 * any additional code,
59 * The callback uses the same signature as middleware, the only difference
60 * being that the value of the placeholder is passed, in this case the _id_
61 * of the user. Once the `next()` function is invoked, just like middleware
62 * it will continue on to execute the route, or subsequent parameter functions.
64 * Just like in middleware, you must either respond to the request or call next
65 * to avoid stalling the request.
67 * app.param('user_id', function(req, res, next, id){
68 * User.find(id, function(err, user){
72 * return next(new Error('failed to load user'));
79 * @param {String} name
80 * @param {Function} fn
81 * @return {app} for chaining
85 proto.param = function param(name, fn) {
87 if (typeof name === 'function') {
88 deprecate('router.param(fn): Refactor to use path params');
89 this._params.push(name);
93 // apply param functions
94 var params = this._params;
95 var len = params.length;
98 if (name[0] === ':') {
99 deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');
100 name = name.substr(1);
103 for (var i = 0; i < len; ++i) {
104 if (ret = params[i](name, fn)) {
109 // ensure we end up with a
110 // middleware function
111 if ('function' != typeof fn) {
112 throw new Error('invalid param() call for ' + name + ', got ' + fn);
115 (this.params[name] = this.params[name] || []).push(fn);
120 * Dispatch a req, res into the router.
125 proto.handle = function(req, res, done) {
128 debug('dispatching %s %s', req.method, req.url);
130 var search = 1 + req.url.indexOf('?');
131 var pathlength = search ? search - 1 : req.url.length;
132 var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
133 var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
136 var slashAdded = false;
137 var paramcalled = {};
139 // store options for OPTIONS request
140 // only used if OPTIONS request
143 // middleware and routes
144 var stack = self.stack;
146 // manage inter-router variables
147 var parentParams = req.params;
148 var parentUrl = req.baseUrl || '';
149 done = restore(done, req, 'baseUrl', 'next', 'params');
154 // for options requests, respond with a default if nothing else responds
155 if (req.method === 'OPTIONS') {
156 done = wrap(done, function(old, err) {
157 if (err || options.length === 0) return old(err);
158 sendOptionsResponse(res, options, old);
162 // setup basic req values
163 req.baseUrl = parentUrl;
164 req.originalUrl = req.originalUrl || req.url;
169 var layerError = err === 'route'
173 // remove added slash
175 req.url = req.url.substr(1);
179 // restore altered req.url
180 if (removed.length !== 0) {
181 req.baseUrl = parentUrl;
182 req.url = protohost + removed + req.url.substr(protohost.length);
186 // no more matching layers
187 if (idx >= stack.length) {
188 setImmediate(done, layerError);
192 // get pathname of request
193 var path = getPathname(req);
196 return done(layerError);
199 // find next matching layer
204 while (match !== true && idx < stack.length) {
205 layer = stack[idx++];
206 match = matchLayer(layer, path);
209 if (typeof match !== 'boolean') {
210 // hold on to layerError
211 layerError = layerError || match;
214 if (match !== true) {
219 // process non-route handlers normally
224 // routes do not match with a pending error
229 var method = req.method;
230 var has_method = route._handles_method(method);
232 // build up automatic options response
233 if (!has_method && method === 'OPTIONS') {
234 appendMethods(options, route._options());
237 // don't even bother matching route
238 if (!has_method && method !== 'HEAD') {
245 if (match !== true) {
246 return done(layerError);
249 // store route for dispatch on change
254 // Capture one-time layer values
255 req.params = self.mergeParams
256 ? mergeParams(layer.params, parentParams)
258 var layerPath = layer.path;
260 // this should be done for the layer
261 self.process_params(layer, paramcalled, req, res, function (err) {
263 return next(layerError || err);
267 return layer.handle_request(req, res, next);
270 trim_prefix(layer, layerError, layerPath, path);
274 function trim_prefix(layer, layerError, layerPath, path) {
275 var c = path[layerPath.length];
276 if (c && '/' !== c && '.' !== c) return next(layerError);
278 // Trim off the part of the url that matches the route
279 // middleware (.use stuff) needs to have the path stripped
280 if (layerPath.length !== 0) {
281 debug('trim prefix (%s) from url %s', layerPath, req.url);
283 req.url = protohost + req.url.substr(protohost.length + removed.length);
285 // Ensure leading slash
286 if (!fqdn && req.url[0] !== '/') {
287 req.url = '/' + req.url;
291 // Setup base URL (no trailing slash)
292 req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
293 ? removed.substring(0, removed.length - 1)
297 debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
300 layer.handle_error(layerError, req, res, next);
302 layer.handle_request(req, res, next);
308 * Process any parameters for the layer.
313 proto.process_params = function(layer, called, req, res, done) {
314 var params = this.params;
316 // captured parameters from the layer, keys and values
317 var keys = layer.keys;
320 if (!keys || keys.length === 0) {
332 // process params in order
333 // param callbacks can be async
334 function param(err) {
339 if (i >= keys.length ) {
351 paramVal = req.params[name];
352 paramCallbacks = params[name];
353 paramCalled = called[name];
355 if (paramVal === undefined || !paramCallbacks) {
359 // param previously called with same value or error occurred
360 if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
362 req.params[name] = paramCalled.value;
365 return param(paramCalled.error);
368 called[name] = paramCalled = {
377 // single param callbacks
378 function paramCallback(err) {
379 var fn = paramCallbacks[paramIndex++];
381 // store updated value
382 paramCalled.value = req.params[key.name];
386 paramCalled.error = err;
391 if (!fn) return param();
394 fn(req, res, paramCallback, paramVal, key.name);
404 * Use the given middleware function, with optional path, defaulting to "/".
406 * Use (like `.all`) will run for any http METHOD, but it will not add
407 * handlers for those methods so OPTIONS requests will not consider `.use`
408 * functions even if they could respond.
410 * The other difference is that _route_ path is stripped and not visible
411 * to the handler function. The main effect of this feature is that mounted
412 * handlers can operate without any code changes regardless of the "prefix"
418 proto.use = function use(fn) {
422 // default path to '/'
423 // disambiguate router.use([fn])
424 if (typeof fn !== 'function') {
427 while (Array.isArray(arg) && arg.length !== 0) {
431 // first arg is the path
432 if (typeof arg !== 'function') {
438 var callbacks = utils.flatten(slice.call(arguments, offset));
440 if (callbacks.length === 0) {
441 throw new TypeError('Router.use() requires middleware functions');
444 callbacks.forEach(function (fn) {
445 if (typeof fn !== 'function') {
446 throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
449 // add the middleware
450 debug('use %s %s', path, fn.name || '<anonymous>');
452 var layer = new Layer(path, {
453 sensitive: this.caseSensitive,
458 layer.route = undefined;
460 this.stack.push(layer);
467 * Create a new Route for the given path.
469 * Each route contains a separate middleware stack and VERB handlers.
471 * See the Route api documentation for details on adding handlers
472 * and middleware to routes.
474 * @param {String} path
479 proto.route = function(path){
480 var route = new Route(path);
482 var layer = new Layer(path, {
483 sensitive: this.caseSensitive,
486 }, route.dispatch.bind(route));
490 this.stack.push(layer);
494 // create Router#VERB functions
495 methods.concat('all').forEach(function(method){
496 proto[method] = function(path){
497 var route = this.route(path)
498 route[method].apply(route, slice.call(arguments, 1));
503 // append methods to a list of methods
504 function appendMethods(list, addition) {
505 for (var i = 0; i < addition.length; i++) {
506 var method = addition[i];
507 if (list.indexOf(method) === -1) {
513 // get pathname of request
514 function getPathname(req) {
516 return parseUrl(req).pathname;
522 // get type for error message
523 function gettype(obj) {
524 var type = typeof obj;
526 if (type !== 'object') {
530 // inspect [[Class]] for objects
531 return toString.call(obj)
532 .replace(objectRegExp, '$1');
536 * Match path to a layer.
538 * @param {Layer} layer
539 * @param {string} path
543 function matchLayer(layer, path) {
545 return layer.match(path);
551 // merge params with parent params
552 function mergeParams(params, parent) {
553 if (typeof parent !== 'object' || !parent) {
557 // make copy of parent for base
558 var obj = mixin({}, parent);
560 // simple non-numeric merging
561 if (!(0 in params) || !(0 in parent)) {
562 return mixin(obj, params);
568 // determine numeric gaps
569 while (i === o || o in parent) {
570 if (i in params) i++;
571 if (o in parent) o++;
574 // offset numeric indices in params before merge
575 for (i--; i >= 0; i--) {
576 params[i + o] = params[i];
578 // create holes for the merge when necessary
584 return mixin(parent, params);
587 // restore obj props after function
588 function restore(fn, obj) {
589 var props = new Array(arguments.length - 2);
590 var vals = new Array(arguments.length - 2);
592 for (var i = 0; i < props.length; i++) {
593 props[i] = arguments[i + 2];
594 vals[i] = obj[props[i]];
597 return function(err){
599 for (var i = 0; i < props.length; i++) {
600 obj[props[i]] = vals[i];
603 return fn.apply(this, arguments);
607 // send an OPTIONS response
608 function sendOptionsResponse(res, options, next) {
610 var body = options.join(',');
611 res.set('Allow', body);
619 function wrap(old, fn) {
620 return function proxy() {
621 var args = new Array(arguments.length + 1);
624 for (var i = 0, len = arguments.length; i < len; i++) {
625 args[i + 1] = arguments[i];
628 fn.apply(this, args);