second
[josuexyz/.git] / node_modules / express / lib / router / index.js
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
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')
25
26 /**
27  * Module variables.
28  * @private
29  */
30
31 var objectRegExp = /^\[object (\S+)\]$/;
32 var slice = Array.prototype.slice;
33 var toString = Object.prototype.toString;
34
35 /**
36  * Initialize a new `Router` with the given `options`.
37  *
38  * @param {Object} [options]
39  * @return {Router} which is an callable function
40  * @public
41  */
42
43 var proto = module.exports = function(options) {
44   var opts = options || {};
45
46   function router(req, res, next) {
47     router.handle(req, res, next);
48   }
49
50   // mixin Router class functions
51   setPrototypeOf(router, proto)
52
53   router.params = {};
54   router._params = [];
55   router.caseSensitive = opts.caseSensitive;
56   router.mergeParams = opts.mergeParams;
57   router.strict = opts.strict;
58   router.stack = [];
59
60   return router;
61 };
62
63 /**
64  * Map the given param placeholder `name`(s) to the given callback.
65  *
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,
70  *
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.
75  *
76  * Just like in middleware, you must either respond to the request or call next
77  * to avoid stalling the request.
78  *
79  *  app.param('user_id', function(req, res, next, id){
80  *    User.find(id, function(err, user){
81  *      if (err) {
82  *        return next(err);
83  *      } else if (!user) {
84  *        return next(new Error('failed to load user'));
85  *      }
86  *      req.user = user;
87  *      next();
88  *    });
89  *  });
90  *
91  * @param {String} name
92  * @param {Function} fn
93  * @return {app} for chaining
94  * @public
95  */
96
97 proto.param = function param(name, fn) {
98   // param logic
99   if (typeof name === 'function') {
100     deprecate('router.param(fn): Refactor to use path params');
101     this._params.push(name);
102     return;
103   }
104
105   // apply param functions
106   var params = this._params;
107   var len = params.length;
108   var ret;
109
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);
113   }
114
115   for (var i = 0; i < len; ++i) {
116     if (ret = params[i](name, fn)) {
117       fn = ret;
118     }
119   }
120
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);
125   }
126
127   (this.params[name] = this.params[name] || []).push(fn);
128   return this;
129 };
130
131 /**
132  * Dispatch a req, res into the router.
133  * @private
134  */
135
136 proto.handle = function handle(req, res, out) {
137   var self = this;
138
139   debug('dispatching %s %s', req.method, req.url);
140
141   var idx = 0;
142   var protohost = getProtohost(req.url) || ''
143   var removed = '';
144   var slashAdded = false;
145   var paramcalled = {};
146
147   // store options for OPTIONS request
148   // only used if OPTIONS request
149   var options = [];
150
151   // middleware and routes
152   var stack = self.stack;
153
154   // manage inter-router variables
155   var parentParams = req.params;
156   var parentUrl = req.baseUrl || '';
157   var done = restore(out, req, 'baseUrl', 'next', 'params');
158
159   // setup next layer
160   req.next = next;
161
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);
167     });
168   }
169
170   // setup basic req values
171   req.baseUrl = parentUrl;
172   req.originalUrl = req.originalUrl || req.url;
173
174   next();
175
176   function next(err) {
177     var layerError = err === 'route'
178       ? null
179       : err;
180
181     // remove added slash
182     if (slashAdded) {
183       req.url = req.url.substr(1);
184       slashAdded = false;
185     }
186
187     // restore altered req.url
188     if (removed.length !== 0) {
189       req.baseUrl = parentUrl;
190       req.url = protohost + removed + req.url.substr(protohost.length);
191       removed = '';
192     }
193
194     // signal to exit router
195     if (layerError === 'router') {
196       setImmediate(done, null)
197       return
198     }
199
200     // no more matching layers
201     if (idx >= stack.length) {
202       setImmediate(done, layerError);
203       return;
204     }
205
206     // get pathname of request
207     var path = getPathname(req);
208
209     if (path == null) {
210       return done(layerError);
211     }
212
213     // find next matching layer
214     var layer;
215     var match;
216     var route;
217
218     while (match !== true && idx < stack.length) {
219       layer = stack[idx++];
220       match = matchLayer(layer, path);
221       route = layer.route;
222
223       if (typeof match !== 'boolean') {
224         // hold on to layerError
225         layerError = layerError || match;
226       }
227
228       if (match !== true) {
229         continue;
230       }
231
232       if (!route) {
233         // process non-route handlers normally
234         continue;
235       }
236
237       if (layerError) {
238         // routes do not match with a pending error
239         match = false;
240         continue;
241       }
242
243       var method = req.method;
244       var has_method = route._handles_method(method);
245
246       // build up automatic options response
247       if (!has_method && method === 'OPTIONS') {
248         appendMethods(options, route._options());
249       }
250
251       // don't even bother matching route
252       if (!has_method && method !== 'HEAD') {
253         match = false;
254         continue;
255       }
256     }
257
258     // no match
259     if (match !== true) {
260       return done(layerError);
261     }
262
263     // store route for dispatch on change
264     if (route) {
265       req.route = route;
266     }
267
268     // Capture one-time layer values
269     req.params = self.mergeParams
270       ? mergeParams(layer.params, parentParams)
271       : layer.params;
272     var layerPath = layer.path;
273
274     // this should be done for the layer
275     self.process_params(layer, paramcalled, req, res, function (err) {
276       if (err) {
277         return next(layerError || err);
278       }
279
280       if (route) {
281         return layer.handle_request(req, res, next);
282       }
283
284       trim_prefix(layer, layerError, layerPath, path);
285     });
286   }
287
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)
293
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);
297       removed = layerPath;
298       req.url = protohost + req.url.substr(protohost.length + removed.length);
299
300       // Ensure leading slash
301       if (!protohost && req.url[0] !== '/') {
302         req.url = '/' + req.url;
303         slashAdded = true;
304       }
305
306       // Setup base URL (no trailing slash)
307       req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
308         ? removed.substring(0, removed.length - 1)
309         : removed);
310     }
311
312     debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
313
314     if (layerError) {
315       layer.handle_error(layerError, req, res, next);
316     } else {
317       layer.handle_request(req, res, next);
318     }
319   }
320 };
321
322 /**
323  * Process any parameters for the layer.
324  * @private
325  */
326
327 proto.process_params = function process_params(layer, called, req, res, done) {
328   var params = this.params;
329
330   // captured parameters from the layer, keys and values
331   var keys = layer.keys;
332
333   // fast track
334   if (!keys || keys.length === 0) {
335     return done();
336   }
337
338   var i = 0;
339   var name;
340   var paramIndex = 0;
341   var key;
342   var paramVal;
343   var paramCallbacks;
344   var paramCalled;
345
346   // process params in order
347   // param callbacks can be async
348   function param(err) {
349     if (err) {
350       return done(err);
351     }
352
353     if (i >= keys.length ) {
354       return done();
355     }
356
357     paramIndex = 0;
358     key = keys[i++];
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
428 proto.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 a middleware function')
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 a middleware function but got a ' + gettype(fn))
459     }
460
461     // add the middleware
462     debug('use %o %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
491 proto.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
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));
511     return this;
512   };
513 });
514
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) {
520       list.push(method);
521     }
522   }
523 }
524
525 // get pathname of request
526 function getPathname(req) {
527   try {
528     return parseUrl(req).pathname;
529   } catch (err) {
530     return undefined;
531   }
532 }
533
534 // Get get protocol + host for a URL
535 function getProtohost(url) {
536   if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
537     return undefined
538   }
539
540   var searchIndex = url.indexOf('?')
541   var pathLength = searchIndex !== -1
542     ? searchIndex
543     : url.length
544   var fqdnIndex = url.substr(0, pathLength).indexOf('://')
545
546   return fqdnIndex !== -1
547     ? url.substr(0, url.indexOf('/', 3 + fqdnIndex))
548     : undefined
549 }
550
551 // get type for error message
552 function gettype(obj) {
553   var type = typeof obj;
554
555   if (type !== 'object') {
556     return type;
557   }
558
559   // inspect [[Class]] for objects
560   return toString.call(obj)
561     .replace(objectRegExp, '$1');
562 }
563
564 /**
565  * Match path to a layer.
566  *
567  * @param {Layer} layer
568  * @param {string} path
569  * @private
570  */
571
572 function matchLayer(layer, path) {
573   try {
574     return layer.match(path);
575   } catch (err) {
576     return err;
577   }
578 }
579
580 // merge params with parent params
581 function mergeParams(params, parent) {
582   if (typeof parent !== 'object' || !parent) {
583     return params;
584   }
585
586   // make copy of parent for base
587   var obj = mixin({}, parent);
588
589   // simple non-numeric merging
590   if (!(0 in params) || !(0 in parent)) {
591     return mixin(obj, params);
592   }
593
594   var i = 0;
595   var o = 0;
596
597   // determine numeric gaps
598   while (i in params) {
599     i++;
600   }
601
602   while (o in parent) {
603     o++;
604   }
605
606   // offset numeric indices in params before merge
607   for (i--; i >= 0; i--) {
608     params[i + o] = params[i];
609
610     // create holes for the merge when necessary
611     if (i < o) {
612       delete params[i];
613     }
614   }
615
616   return mixin(obj, params);
617 }
618
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);
623
624   for (var i = 0; i < props.length; i++) {
625     props[i] = arguments[i + 2];
626     vals[i] = obj[props[i]];
627   }
628
629   return function () {
630     // restore vals
631     for (var i = 0; i < props.length; i++) {
632       obj[props[i]] = vals[i];
633     }
634
635     return fn.apply(this, arguments);
636   };
637 }
638
639 // send an OPTIONS response
640 function sendOptionsResponse(res, options, next) {
641   try {
642     var body = options.join(',');
643     res.set('Allow', body);
644     res.send(body);
645   } catch (err) {
646     next(err);
647   }
648 }
649
650 // wrap a function
651 function wrap(old, fn) {
652   return function proxy() {
653     var args = new Array(arguments.length + 1);
654
655     args[0] = old;
656     for (var i = 0, len = arguments.length; i < len; i++) {
657       args[i + 1] = arguments[i];
658     }
659
660     fn.apply(this, args);
661   };
662 }