arnaucube
arnaucube/thoughtsPublic Self-hosted repository
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}