github.com/solo-io/unik@v0.0.0-20190717152701-a58d3e8e33b7/docs/examples/example-nodejs-fileserver/node_modules/express/lib/router/index.js (about)

     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  
    25  /**
    26   * Module variables.
    27   * @private
    28   */
    29  
    30  var objectRegExp = /^\[object (\S+)\]$/;
    31  var slice = Array.prototype.slice;
    32  var 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  
    42  var 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  
    96  proto.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  
   135  proto.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  
   322  proto.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  
   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 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  
   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 type for error message
   535  function 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  
   555  function 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
   564  function 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
   603  function 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
   623  function 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
   634  function 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  }