thoughts/node_modules/express/lib/router/index.js
node_modules/express/lib/router/index.jsRaw
1/*!
2 * express
3 * Copyright(c) 2009-2013 TJ Holowaychuk
4 * Copyright(c) 2013 Roman Shtylman
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
6 * MIT Licensed
7 */
8
9'use strict';
10
11/**
12 * Module dependencies.
13 * @private
14 */
15
16var Route = require('./route');
17var Layer = require('./layer');
18var methods = require('methods');
19var mixin = require('utils-merge');
20var debug = require('debug')('express:router');
21var deprecate = require('depd')('express');
22var flatten = require('array-flatten');
23var parseUrl = require('parseurl');
24
25/**
26 * Module variables.
27 * @private
28 */
29
30var objectRegExp = /^\[object (\S+)\]$/;
31var slice = Array.prototype.slice;
32var toString = Object.prototype.toString;
33
34/**
35 * Initialize a new `Router` with the given `options`.
36 *
37 * @param {Object} options
38 * @return {Router} which is an callable function
39 * @public
40 */
41
42var proto = module.exports = function(options) {
43 var opts = options || {};
44
45 function router(req, res, next) {
46 router.handle(req, res, next);
47 }
48
49 // mixin Router class functions
50 router.__proto__ = proto;
51
52 router.params = {};
53 router._params = [];
54 router.caseSensitive = opts.caseSensitive;
55 router.mergeParams = opts.mergeParams;
56 router.strict = opts.strict;
57 router.stack = [];
58
59 return router;
60};
61
62/**
63 * Map the given param placeholder `name`(s) to the given callback.
64 *
65 * Parameter mapping is used to provide pre-conditions to routes
66 * which use normalized placeholders. For example a _:user_id_ parameter
67 * could automatically load a user's information from the database without
68 * any additional code,
69 *
70 * The callback uses the same signature as middleware, the only difference
71 * being that the value of the placeholder is passed, in this case the _id_
72 * of the user. Once the `next()` function is invoked, just like middleware
73 * it will continue on to execute the route, or subsequent parameter functions.
74 *
75 * Just like in middleware, you must either respond to the request or call next
76 * to avoid stalling the request.
77 *
78 * app.param('user_id', function(req, res, next, id){
79 * User.find(id, function(err, user){
80 * if (err) {
81 * return next(err);
82 * } else if (!user) {
83 * return next(new Error('failed to load user'));
84 * }
85 * req.user = user;
86 * next();
87 * });
88 * });
89 *
90 * @param {String} name
91 * @param {Function} fn
92 * @return {app} for chaining
93 * @public
94 */
95
96proto.param = function param(name, fn) {
97 // param logic
98 if (typeof name === 'function') {
99 deprecate('router.param(fn): Refactor to use path params');
100 this._params.push(name);
101 return;
102 }
103
104 // apply param functions
105 var params = this._params;
106 var len = params.length;
107 var ret;
108
109 if (name[0] === ':') {
110 deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');
111 name = name.substr(1);
112 }
113
114 for (var i = 0; i < len; ++i) {
115 if (ret = params[i](name, fn)) {
116 fn = ret;
117 }
118 }
119
120 // ensure we end up with a
121 // middleware function
122 if ('function' !== typeof fn) {
123 throw new Error('invalid param() call for ' + name + ', got ' + fn);
124 }
125
126 (this.params[name] = this.params[name] || []).push(fn);
127 return this;
128};
129
130/**
131 * Dispatch a req, res into the router.
132 * @private
133 */
134
135proto.handle = function handle(req, res, out) {
136 var self = this;
137
138 debug('dispatching %s %s', req.method, req.url);
139
140 var search = 1 + req.url.indexOf('?');
141 var pathlength = search ? search - 1 : req.url.length;
142 var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
143 var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
144 var idx = 0;
145 var removed = '';
146 var slashAdded = false;
147 var paramcalled = {};
148
149 // store options for OPTIONS request
150 // only used if OPTIONS request
151 var options = [];
152
153 // middleware and routes
154 var stack = self.stack;
155
156 // manage inter-router variables
157 var parentParams = req.params;
158 var parentUrl = req.baseUrl || '';
159 var done = restore(out, req, 'baseUrl', 'next', 'params');
160
161 // setup next layer
162 req.next = next;
163
164 // for options requests, respond with a default if nothing else responds
165 if (req.method === 'OPTIONS') {
166 done = wrap(done, function(old, err) {
167 if (err || options.length === 0) return old(err);
168 sendOptionsResponse(res, options, old);
169 });
170 }
171
172 // setup basic req values
173 req.baseUrl = parentUrl;
174 req.originalUrl = req.originalUrl || req.url;
175
176 next();
177
178 function next(err) {
179 var layerError = err === 'route'
180 ? null
181 : err;
182
183 // remove added slash
184 if (slashAdded) {
185 req.url = req.url.substr(1);
186 slashAdded = false;
187 }
188
189 // restore altered req.url
190 if (removed.length !== 0) {
191 req.baseUrl = parentUrl;
192 req.url = protohost + removed + req.url.substr(protohost.length);
193 removed = '';
194 }
195
196 // no more matching layers
197 if (idx >= stack.length) {
198 setImmediate(done, layerError);
199 return;
200 }
201
202 // get pathname of request
203 var path = getPathname(req);
204
205 if (path == null) {
206 return done(layerError);
207 }
208
209 // find next matching layer
210 var layer;
211 var match;
212 var route;
213
214 while (match !== true && idx < stack.length) {
215 layer = stack[idx++];
216 match = matchLayer(layer, path);
217 route = layer.route;
218
219 if (typeof match !== 'boolean') {
220 // hold on to layerError
221 layerError = layerError || match;
222 }
223
224 if (match !== true) {
225 continue;
226 }
227
228 if (!route) {
229 // process non-route handlers normally
230 continue;
231 }
232
233 if (layerError) {
234 // routes do not match with a pending error
235 match = false;
236 continue;
237 }
238
239 var method = req.method;
240 var has_method = route._handles_method(method);
241
242 // build up automatic options response
243 if (!has_method && method === 'OPTIONS') {
244 appendMethods(options, route._options());
245 }
246
247 // don't even bother matching route
248 if (!has_method && method !== 'HEAD') {
249 match = false;
250 continue;
251 }
252 }
253
254 // no match
255 if (match !== true) {
256 return done(layerError);
257 }
258
259 // store route for dispatch on change
260 if (route) {
261 req.route = route;
262 }
263
264 // Capture one-time layer values
265 req.params = self.mergeParams
266 ? mergeParams(layer.params, parentParams)
267 : layer.params;
268 var layerPath = layer.path;
269
270 // this should be done for the layer
271 self.process_params(layer, paramcalled, req, res, function (err) {
272 if (err) {
273 return next(layerError || err);
274 }
275
276 if (route) {
277 return layer.handle_request(req, res, next);
278 }
279
280 trim_prefix(layer, layerError, layerPath, path);
281 });
282 }
283
284 function trim_prefix(layer, layerError, layerPath, path) {
285 var c = path[layerPath.length];
286 if (c && '/' !== c && '.' !== c) return next(layerError);
287
288 // Trim off the part of the url that matches the route
289 // middleware (.use stuff) needs to have the path stripped
290 if (layerPath.length !== 0) {
291 debug('trim prefix (%s) from url %s', layerPath, req.url);
292 removed = layerPath;
293 req.url = protohost + req.url.substr(protohost.length + removed.length);
294
295 // Ensure leading slash
296 if (!fqdn && req.url[0] !== '/') {
297 req.url = '/' + req.url;
298 slashAdded = true;
299 }
300
301 // Setup base URL (no trailing slash)
302 req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
303 ? removed.substring(0, removed.length - 1)
304 : removed);
305 }
306
307 debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
308
309 if (layerError) {
310 layer.handle_error(layerError, req, res, next);
311 } else {
312 layer.handle_request(req, res, next);
313 }
314 }
315};
316
317/**
318 * Process any parameters for the layer.
319 * @private
320 */
321
322proto.process_params = function process_params(layer, called, req, res, done) {
323 var params = this.params;
324
325 // captured parameters from the layer, keys and values
326 var keys = layer.keys;
327
328 // fast track
329 if (!keys || keys.length === 0) {
330 return done();
331 }
332
333 var i = 0;
334 var name;
335 var paramIndex = 0;
336 var key;
337 var paramVal;
338 var paramCallbacks;
339 var paramCalled;
340
341 // process params in order
342 // param callbacks can be async
343 function param(err) {
344 if (err) {
345 return done(err);
346 }
347
348 if (i >= keys.length ) {
349 return done();
350 }
351
352 paramIndex = 0;
353 key = keys[i++];
354
355 if (!key) {
356 return done();
357 }
358
359 name = key.name;
360 paramVal = req.params[name];
361 paramCallbacks = params[name];
362 paramCalled = called[name];
363
364 if (paramVal === undefined || !paramCallbacks) {
365 return param();
366 }
367
368 // param previously called with same value or error occurred
369 if (paramCalled && (paramCalled.match === paramVal
370 || (paramCalled.error && paramCalled.error !== 'route'))) {
371 // restore value
372 req.params[name] = paramCalled.value;
373
374 // next param
375 return param(paramCalled.error);
376 }
377
378 called[name] = paramCalled = {
379 error: null,
380 match: paramVal,
381 value: paramVal
382 };
383
384 paramCallback();
385 }
386
387 // single param callbacks
388 function paramCallback(err) {
389 var fn = paramCallbacks[paramIndex++];
390
391 // store updated value
392 paramCalled.value = req.params[key.name];
393
394 if (err) {
395 // store error
396 paramCalled.error = err;
397 param(err);
398 return;
399 }
400
401 if (!fn) return param();
402
403 try {
404 fn(req, res, paramCallback, paramVal, key.name);
405 } catch (e) {
406 paramCallback(e);
407 }
408 }
409
410 param();
411};
412
413/**
414 * Use the given middleware function, with optional path, defaulting to "/".
415 *
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.
419 *
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"
423 * pathname.
424 *
425 * @public
426 */
427
428proto.use = function use(fn) {
429 var offset = 0;
430 var path = '/';
431
432 // default path to '/'
433 // disambiguate router.use([fn])
434 if (typeof fn !== 'function') {
435 var arg = fn;
436
437 while (Array.isArray(arg) && arg.length !== 0) {
438 arg = arg[0];
439 }
440
441 // first arg is the path
442 if (typeof arg !== 'function') {
443 offset = 1;
444 path = fn;
445 }
446 }
447
448 var callbacks = flatten(slice.call(arguments, offset));
449
450 if (callbacks.length === 0) {
451 throw new TypeError('Router.use() requires middleware functions');
452 }
453
454 for (var i = 0; i < callbacks.length; i++) {
455 var fn = callbacks[i];
456
457 if (typeof fn !== 'function') {
458 throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
459 }
460
461 // add the middleware
462 debug('use %s %s', path, fn.name || '<anonymous>');
463
464 var layer = new Layer(path, {
465 sensitive: this.caseSensitive,
466 strict: false,
467 end: false
468 }, fn);
469
470 layer.route = undefined;
471
472 this.stack.push(layer);
473 }
474
475 return this;
476};
477
478/**
479 * Create a new Route for the given path.
480 *
481 * Each route contains a separate middleware stack and VERB handlers.
482 *
483 * See the Route api documentation for details on adding handlers
484 * and middleware to routes.
485 *
486 * @param {String} path
487 * @return {Route}
488 * @public
489 */
490
491proto.route = function route(path) {
492 var route = new Route(path);
493
494 var layer = new Layer(path, {
495 sensitive: this.caseSensitive,
496 strict: this.strict,
497 end: true
498 }, route.dispatch.bind(route));
499
500 layer.route = route;
501
502 this.stack.push(layer);
503 return route;
504};
505
506// create Router#VERB functions
507methods.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));
511 return this;
512 };
513});
514
515// append methods to a list of methods
516function appendMethods(list, addition) {
517 for (var i = 0; i < addition.length; i++) {
518 var method = addition[i];
519 if (list.indexOf(method) === -1) {
520 list.push(method);
521 }
522 }
523}
524
525// get pathname of request
526function getPathname(req) {
527 try {
528 return parseUrl(req).pathname;
529 } catch (err) {
530 return undefined;
531 }
532}
533
534// get type for error message
535function gettype(obj) {
536 var type = typeof obj;
537
538 if (type !== 'object') {
539 return type;
540 }
541
542 // inspect [[Class]] for objects
543 return toString.call(obj)
544 .replace(objectRegExp, '$1');
545}
546
547/**
548 * Match path to a layer.
549 *
550 * @param {Layer} layer
551 * @param {string} path
552 * @private
553 */
554
555function matchLayer(layer, path) {
556 try {
557 return layer.match(path);
558 } catch (err) {
559 return err;
560 }
561}
562
563// merge params with parent params
564function mergeParams(params, parent) {
565 if (typeof parent !== 'object' || !parent) {
566 return params;
567 }
568
569 // make copy of parent for base
570 var obj = mixin({}, parent);
571
572 // simple non-numeric merging
573 if (!(0 in params) || !(0 in parent)) {
574 return mixin(obj, params);
575 }
576
577 var i = 0;
578 var o = 0;
579
580 // determine numeric gaps
581 while (i in params) {
582 i++;
583 }
584
585 while (o in parent) {
586 o++;
587 }
588
589 // offset numeric indices in params before merge
590 for (i--; i >= 0; i--) {
591 params[i + o] = params[i];
592
593 // create holes for the merge when necessary
594 if (i < o) {
595 delete params[i];
596 }
597 }
598
599 return mixin(obj, params);
600}
601
602// restore obj props after function
603function restore(fn, obj) {
604 var props = new Array(arguments.length - 2);
605 var vals = new Array(arguments.length - 2);
606
607 for (var i = 0; i < props.length; i++) {
608 props[i] = arguments[i + 2];
609 vals[i] = obj[props[i]];
610 }
611
612 return function(err){
613 // restore vals
614 for (var i = 0; i < props.length; i++) {
615 obj[props[i]] = vals[i];
616 }
617
618 return fn.apply(this, arguments);
619 };
620}
621
622// send an OPTIONS response
623function sendOptionsResponse(res, options, next) {
624 try {
625 var body = options.join(',');
626 res.set('Allow', body);
627 res.send(body);
628 } catch (err) {
629 next(err);
630 }
631}
632
633// wrap a function
634function wrap(old, fn) {
635 return function proxy() {
636 var args = new Array(arguments.length + 1);
637
638 args[0] = old;
639 for (var i = 0, len = arguments.length; i < len; i++) {
640 args[i + 1] = arguments[i];
641 }
642
643 fn.apply(this, args);
644 };
645}