3 * Copyright(c) 2009-2013 TJ Holowaychuk
4 * Copyright(c) 2013 Roman Shtylman
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
12 * Module dependencies.
16 var Route = require('./route');
17 var Layer = require('./layer');
18 var methods = require('methods');
19 var mixin = require('utils-merge');
20 var debug = require('debug')('express:router');
21 var deprecate = require('depd')('express');
22 var flatten = require('array-flatten');
23 var parseUrl = require('parseurl');
24 var setPrototypeOf = require('setprototypeof')
31 var objectRegExp = /^\[object (\S+)\]$/;
32 var slice = Array.prototype.slice;
33 var toString = Object.prototype.toString;
36 * Initialize a new `Router` with the given `options`.
38 * @param {Object} [options]
39 * @return {Router} which is an callable function
43 var proto = module.exports = function(options) {
44 var opts = options || {};
46 function router(req, res, next) {
47 router.handle(req, res, next);
50 // mixin Router class functions
51 setPrototypeOf(router, proto)
55 router.caseSensitive = opts.caseSensitive;
56 router.mergeParams = opts.mergeParams;
57 router.strict = opts.strict;
64 * Map the given param placeholder `name`(s) to the given callback.
66 * Parameter mapping is used to provide pre-conditions to routes
67 * which use normalized placeholders. For example a _:user_id_ parameter
68 * could automatically load a user's information from the database without
69 * any additional code,
71 * The callback uses the same signature as middleware, the only difference
72 * being that the value of the placeholder is passed, in this case the _id_
73 * of the user. Once the `next()` function is invoked, just like middleware
74 * it will continue on to execute the route, or subsequent parameter functions.
76 * Just like in middleware, you must either respond to the request or call next
77 * to avoid stalling the request.
79 * app.param('user_id', function(req, res, next, id){
80 * User.find(id, function(err, user){
84 * return next(new Error('failed to load user'));
91 * @param {String} name
92 * @param {Function} fn
93 * @return {app} for chaining
97 proto.param = function param(name, fn) {
99 if (typeof name === 'function') {
100 deprecate('router.param(fn): Refactor to use path params');
101 this._params.push(name);
105 // apply param functions
106 var params = this._params;
107 var len = params.length;
110 if (name[0] === ':') {
111 deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');
112 name = name.substr(1);
115 for (var i = 0; i < len; ++i) {
116 if (ret = params[i](name, fn)) {
121 // ensure we end up with a
122 // middleware function
123 if ('function' !== typeof fn) {
124 throw new Error('invalid param() call for ' + name + ', got ' + fn);
127 (this.params[name] = this.params[name] || []).push(fn);
132 * Dispatch a req, res into the router.
136 proto.handle = function handle(req, res, out) {
139 debug('dispatching %s %s', req.method, req.url);
142 var protohost = getProtohost(req.url) || ''
144 var slashAdded = false;
145 var paramcalled = {};
147 // store options for OPTIONS request
148 // only used if OPTIONS request
151 // middleware and routes
152 var stack = self.stack;
154 // manage inter-router variables
155 var parentParams = req.params;
156 var parentUrl = req.baseUrl || '';
157 var done = restore(out, req, 'baseUrl', 'next', 'params');
162 // for options requests, respond with a default if nothing else responds
163 if (req.method === 'OPTIONS') {
164 done = wrap(done, function(old, err) {
165 if (err || options.length === 0) return old(err);
166 sendOptionsResponse(res, options, old);
170 // setup basic req values
171 req.baseUrl = parentUrl;
172 req.originalUrl = req.originalUrl || req.url;
177 var layerError = err === 'route'
181 // remove added slash
183 req.url = req.url.substr(1);
187 // restore altered req.url
188 if (removed.length !== 0) {
189 req.baseUrl = parentUrl;
190 req.url = protohost + removed + req.url.substr(protohost.length);
194 // signal to exit router
195 if (layerError === 'router') {
196 setImmediate(done, null)
200 // no more matching layers
201 if (idx >= stack.length) {
202 setImmediate(done, layerError);
206 // get pathname of request
207 var path = getPathname(req);
210 return done(layerError);
213 // find next matching layer
218 while (match !== true && idx < stack.length) {
219 layer = stack[idx++];
220 match = matchLayer(layer, path);
223 if (typeof match !== 'boolean') {
224 // hold on to layerError
225 layerError = layerError || match;
228 if (match !== true) {
233 // process non-route handlers normally
238 // routes do not match with a pending error
243 var method = req.method;
244 var has_method = route._handles_method(method);
246 // build up automatic options response
247 if (!has_method && method === 'OPTIONS') {
248 appendMethods(options, route._options());
251 // don't even bother matching route
252 if (!has_method && method !== 'HEAD') {
259 if (match !== true) {
260 return done(layerError);
263 // store route for dispatch on change
268 // Capture one-time layer values
269 req.params = self.mergeParams
270 ? mergeParams(layer.params, parentParams)
272 var layerPath = layer.path;
274 // this should be done for the layer
275 self.process_params(layer, paramcalled, req, res, function (err) {
277 return next(layerError || err);
281 return layer.handle_request(req, res, next);
284 trim_prefix(layer, layerError, layerPath, path);
288 function trim_prefix(layer, layerError, layerPath, path) {
289 if (layerPath.length !== 0) {
290 // Validate path breaks on a path separator
291 var c = path[layerPath.length]
292 if (c && c !== '/' && c !== '.') return next(layerError)
294 // Trim off the part of the url that matches the route
295 // middleware (.use stuff) needs to have the path stripped
296 debug('trim prefix (%s) from url %s', layerPath, req.url);
298 req.url = protohost + req.url.substr(protohost.length + removed.length);
300 // Ensure leading slash
301 if (!protohost && req.url[0] !== '/') {
302 req.url = '/' + req.url;
306 // Setup base URL (no trailing slash)
307 req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
308 ? removed.substring(0, removed.length - 1)
312 debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
315 layer.handle_error(layerError, req, res, next);
317 layer.handle_request(req, res, next);
323 * Process any parameters for the layer.
327 proto.process_params = function process_params(layer, called, req, res, done) {
328 var params = this.params;
330 // captured parameters from the layer, keys and values
331 var keys = layer.keys;
334 if (!keys || keys.length === 0) {
346 // process params in order
347 // param callbacks can be async
348 function param(err) {
353 if (i >= keys.length ) {
360 paramVal = req.params[name];
361 paramCallbacks = params[name];
362 paramCalled = called[name];
364 if (paramVal === undefined || !paramCallbacks) {
368 // param previously called with same value or error occurred
369 if (paramCalled && (paramCalled.match === paramVal
370 || (paramCalled.error && paramCalled.error !== 'route'))) {
372 req.params[name] = paramCalled.value;
375 return param(paramCalled.error);
378 called[name] = paramCalled = {
387 // single param callbacks
388 function paramCallback(err) {
389 var fn = paramCallbacks[paramIndex++];
391 // store updated value
392 paramCalled.value = req.params[key.name];
396 paramCalled.error = err;
401 if (!fn) return param();
404 fn(req, res, paramCallback, paramVal, key.name);
414 * Use the given middleware function, with optional path, defaulting to "/".
416 * Use (like `.all`) will run for any http METHOD, but it will not add
417 * handlers for those methods so OPTIONS requests will not consider `.use`
418 * functions even if they could respond.
420 * The other difference is that _route_ path is stripped and not visible
421 * to the handler function. The main effect of this feature is that mounted
422 * handlers can operate without any code changes regardless of the "prefix"
428 proto.use = function use(fn) {
432 // default path to '/'
433 // disambiguate router.use([fn])
434 if (typeof fn !== 'function') {
437 while (Array.isArray(arg) && arg.length !== 0) {
441 // first arg is the path
442 if (typeof arg !== 'function') {
448 var callbacks = flatten(slice.call(arguments, offset));
450 if (callbacks.length === 0) {
451 throw new TypeError('Router.use() requires a middleware function')
454 for (var i = 0; i < callbacks.length; i++) {
455 var fn = callbacks[i];
457 if (typeof fn !== 'function') {
458 throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
461 // add the middleware
462 debug('use %o %s', path, fn.name || '<anonymous>')
464 var layer = new Layer(path, {
465 sensitive: this.caseSensitive,
470 layer.route = undefined;
472 this.stack.push(layer);
479 * Create a new Route for the given path.
481 * Each route contains a separate middleware stack and VERB handlers.
483 * See the Route api documentation for details on adding handlers
484 * and middleware to routes.
486 * @param {String} path
491 proto.route = function route(path) {
492 var route = new Route(path);
494 var layer = new Layer(path, {
495 sensitive: this.caseSensitive,
498 }, route.dispatch.bind(route));
502 this.stack.push(layer);
506 // create Router#VERB functions
507 methods.concat('all').forEach(function(method){
508 proto[method] = function(path){
509 var route = this.route(path)
510 route[method].apply(route, slice.call(arguments, 1));
515 // append methods to a list of methods
516 function appendMethods(list, addition) {
517 for (var i = 0; i < addition.length; i++) {
518 var method = addition[i];
519 if (list.indexOf(method) === -1) {
525 // get pathname of request
526 function getPathname(req) {
528 return parseUrl(req).pathname;
534 // Get get protocol + host for a URL
535 function getProtohost(url) {
536 if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
540 var searchIndex = url.indexOf('?')
541 var pathLength = searchIndex !== -1
544 var fqdnIndex = url.substr(0, pathLength).indexOf('://')
546 return fqdnIndex !== -1
547 ? url.substr(0, url.indexOf('/', 3 + fqdnIndex))
551 // get type for error message
552 function gettype(obj) {
553 var type = typeof obj;
555 if (type !== 'object') {
559 // inspect [[Class]] for objects
560 return toString.call(obj)
561 .replace(objectRegExp, '$1');
565 * Match path to a layer.
567 * @param {Layer} layer
568 * @param {string} path
572 function matchLayer(layer, path) {
574 return layer.match(path);
580 // merge params with parent params
581 function mergeParams(params, parent) {
582 if (typeof parent !== 'object' || !parent) {
586 // make copy of parent for base
587 var obj = mixin({}, parent);
589 // simple non-numeric merging
590 if (!(0 in params) || !(0 in parent)) {
591 return mixin(obj, params);
597 // determine numeric gaps
598 while (i in params) {
602 while (o in parent) {
606 // offset numeric indices in params before merge
607 for (i--; i >= 0; i--) {
608 params[i + o] = params[i];
610 // create holes for the merge when necessary
616 return mixin(obj, params);
619 // restore obj props after function
620 function restore(fn, obj) {
621 var props = new Array(arguments.length - 2);
622 var vals = new Array(arguments.length - 2);
624 for (var i = 0; i < props.length; i++) {
625 props[i] = arguments[i + 2];
626 vals[i] = obj[props[i]];
631 for (var i = 0; i < props.length; i++) {
632 obj[props[i]] = vals[i];
635 return fn.apply(this, arguments);
639 // send an OPTIONS response
640 function sendOptionsResponse(res, options, next) {
642 var body = options.join(',');
643 res.set('Allow', body);
651 function wrap(old, fn) {
652 return function proxy() {
653 var args = new Array(arguments.length + 1);
656 for (var i = 0, len = arguments.length; i < len; i++) {
657 args[i + 1] = arguments[i];
660 fn.apply(this, args);