| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 | /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */'use strict';/** * Module dependencies. * @private */var Route = require('./route');var Layer = require('./layer');var methods = require('methods');var mixin = require('utils-merge');var debug = require('debug')('express:router');var deprecate = require('depd')('express');var flatten = require('array-flatten');var parseUrl = require('parseurl');var setPrototypeOf = require('setprototypeof')/** * Module variables. * @private */var objectRegExp = /^\[object (\S+)\]$/;var slice = Array.prototype.slice;var toString = Object.prototype.toString;/** * Initialize a new `Router` with the given `options`. * * @param {Object} [options] * @return {Router} which is a callable function * @public */var proto = module.exports = function(options) {  var opts = options || {};  function router(req, res, next) {    router.handle(req, res, next);  }  // mixin Router class functions  setPrototypeOf(router, proto)  router.params = {};  router._params = [];  router.caseSensitive = opts.caseSensitive;  router.mergeParams = opts.mergeParams;  router.strict = opts.strict;  router.stack = [];  return router;};/** * Map the given param placeholder `name`(s) to the given callback. * * Parameter mapping is used to provide pre-conditions to routes * which use normalized placeholders. For example a _:user_id_ parameter * could automatically load a user's information from the database without * any additional code, * * The callback uses the same signature as middleware, the only difference * being that the value of the placeholder is passed, in this case the _id_ * of the user. Once the `next()` function is invoked, just like middleware * it will continue on to execute the route, or subsequent parameter functions. * * Just like in middleware, you must either respond to the request or call next * to avoid stalling the request. * *  app.param('user_id', function(req, res, next, id){ *    User.find(id, function(err, user){ *      if (err) { *        return next(err); *      } else if (!user) { *        return next(new Error('failed to load user')); *      } *      req.user = user; *      next(); *    }); *  }); * * @param {String} name * @param {Function} fn * @return {app} for chaining * @public */proto.param = function param(name, fn) {  // param logic  if (typeof name === 'function') {    deprecate('router.param(fn): Refactor to use path params');    this._params.push(name);    return;  }  // apply param functions  var params = this._params;  var len = params.length;  var ret;  if (name[0] === ':') {    deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.slice(1)) + ', fn) instead')    name = name.slice(1)  }  for (var i = 0; i < len; ++i) {    if (ret = params[i](name, fn)) {      fn = ret;    }  }  // ensure we end up with a  // middleware function  if ('function' !== typeof fn) {    throw new Error('invalid param() call for ' + name + ', got ' + fn);  }  (this.params[name] = this.params[name] || []).push(fn);  return this;};/** * Dispatch a req, res into the router. * @private */proto.handle = function handle(req, res, out) {  var self = this;  debug('dispatching %s %s', req.method, req.url);  var idx = 0;  var protohost = getProtohost(req.url) || ''  var removed = '';  var slashAdded = false;  var sync = 0  var paramcalled = {};  // store options for OPTIONS request  // only used if OPTIONS request  var options = [];  // middleware and routes  var stack = self.stack;  // manage inter-router variables  var parentParams = req.params;  var parentUrl = req.baseUrl || '';  var done = restore(out, req, 'baseUrl', 'next', 'params');  // setup next layer  req.next = next;  // for options requests, respond with a default if nothing else responds  if (req.method === 'OPTIONS') {    done = wrap(done, function(old, err) {      if (err || options.length === 0) return old(err);      sendOptionsResponse(res, options, old);    });  }  // setup basic req values  req.baseUrl = parentUrl;  req.originalUrl = req.originalUrl || req.url;  next();  function next(err) {    var layerError = err === 'route'      ? null      : err;    // remove added slash    if (slashAdded) {      req.url = req.url.slice(1)      slashAdded = false;    }    // restore altered req.url    if (removed.length !== 0) {      req.baseUrl = parentUrl;      req.url = protohost + removed + req.url.slice(protohost.length)      removed = '';    }    // signal to exit router    if (layerError === 'router') {      setImmediate(done, null)      return    }    // no more matching layers    if (idx >= stack.length) {      setImmediate(done, layerError);      return;    }    // max sync stack    if (++sync > 100) {      return setImmediate(next, err)    }    // get pathname of request    var path = getPathname(req);    if (path == null) {      return done(layerError);    }    // find next matching layer    var layer;    var match;    var route;    while (match !== true && idx < stack.length) {      layer = stack[idx++];      match = matchLayer(layer, path);      route = layer.route;      if (typeof match !== 'boolean') {        // hold on to layerError        layerError = layerError || match;      }      if (match !== true) {        continue;      }      if (!route) {        // process non-route handlers normally        continue;      }      if (layerError) {        // routes do not match with a pending error        match = false;        continue;      }      var method = req.method;      var has_method = route._handles_method(method);      // build up automatic options response      if (!has_method && method === 'OPTIONS') {        appendMethods(options, route._options());      }      // don't even bother matching route      if (!has_method && method !== 'HEAD') {        match = false;      }    }    // no match    if (match !== true) {      return done(layerError);    }    // store route for dispatch on change    if (route) {      req.route = route;    }    // Capture one-time layer values    req.params = self.mergeParams      ? mergeParams(layer.params, parentParams)      : layer.params;    var layerPath = layer.path;    // this should be done for the layer    self.process_params(layer, paramcalled, req, res, function (err) {      if (err) {        next(layerError || err)      } else if (route) {        layer.handle_request(req, res, next)      } else {        trim_prefix(layer, layerError, layerPath, path)      }      sync = 0    });  }  function trim_prefix(layer, layerError, layerPath, path) {    if (layerPath.length !== 0) {      // Validate path is a prefix match      if (layerPath !== path.slice(0, layerPath.length)) {        next(layerError)        return      }      // Validate path breaks on a path separator      var c = path[layerPath.length]      if (c && c !== '/' && c !== '.') return next(layerError)      // Trim off the part of the url that matches the route      // middleware (.use stuff) needs to have the path stripped      debug('trim prefix (%s) from url %s', layerPath, req.url);      removed = layerPath;      req.url = protohost + req.url.slice(protohost.length + removed.length)      // Ensure leading slash      if (!protohost && req.url[0] !== '/') {        req.url = '/' + req.url;        slashAdded = true;      }      // Setup base URL (no trailing slash)      req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'        ? removed.substring(0, removed.length - 1)        : removed);    }    debug('%s %s : %s', layer.name, layerPath, req.originalUrl);    if (layerError) {      layer.handle_error(layerError, req, res, next);    } else {      layer.handle_request(req, res, next);    }  }};/** * Process any parameters for the layer. * @private */proto.process_params = function process_params(layer, called, req, res, done) {  var params = this.params;  // captured parameters from the layer, keys and values  var keys = layer.keys;  // fast track  if (!keys || keys.length === 0) {    return done();  }  var i = 0;  var name;  var paramIndex = 0;  var key;  var paramVal;  var paramCallbacks;  var paramCalled;  // process params in order  // param callbacks can be async  function param(err) {    if (err) {      return done(err);    }    if (i >= keys.length ) {      return done();    }    paramIndex = 0;    key = keys[i++];    name = key.name;    paramVal = req.params[name];    paramCallbacks = params[name];    paramCalled = called[name];    if (paramVal === undefined || !paramCallbacks) {      return param();    }    // param previously called with same value or error occurred    if (paramCalled && (paramCalled.match === paramVal      || (paramCalled.error && paramCalled.error !== 'route'))) {      // restore value      req.params[name] = paramCalled.value;      // next param      return param(paramCalled.error);    }    called[name] = paramCalled = {      error: null,      match: paramVal,      value: paramVal    };    paramCallback();  }  // single param callbacks  function paramCallback(err) {    var fn = paramCallbacks[paramIndex++];    // store updated value    paramCalled.value = req.params[key.name];    if (err) {      // store error      paramCalled.error = err;      param(err);      return;    }    if (!fn) return param();    try {      fn(req, res, paramCallback, paramVal, key.name);    } catch (e) {      paramCallback(e);    }  }  param();};/** * Use the given middleware function, with optional path, defaulting to "/". * * Use (like `.all`) will run for any http METHOD, but it will not add * handlers for those methods so OPTIONS requests will not consider `.use` * functions even if they could respond. * * The other difference is that _route_ path is stripped and not visible * to the handler function. The main effect of this feature is that mounted * handlers can operate without any code changes regardless of the "prefix" * pathname. * * @public */proto.use = function use(fn) {  var offset = 0;  var path = '/';  // default path to '/'  // disambiguate router.use([fn])  if (typeof fn !== 'function') {    var arg = fn;    while (Array.isArray(arg) && arg.length !== 0) {      arg = arg[0];    }    // first arg is the path    if (typeof arg !== 'function') {      offset = 1;      path = fn;    }  }  var callbacks = flatten(slice.call(arguments, offset));  if (callbacks.length === 0) {    throw new TypeError('Router.use() requires a middleware function')  }  for (var i = 0; i < callbacks.length; i++) {    var fn = callbacks[i];    if (typeof fn !== 'function') {      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))    }    // add the middleware    debug('use %o %s', path, fn.name || '<anonymous>')    var layer = new Layer(path, {      sensitive: this.caseSensitive,      strict: false,      end: false    }, fn);    layer.route = undefined;    this.stack.push(layer);  }  return this;};/** * Create a new Route for the given path. * * Each route contains a separate middleware stack and VERB handlers. * * See the Route api documentation for details on adding handlers * and middleware to routes. * * @param {String} path * @return {Route} * @public */proto.route = function route(path) {  var route = new Route(path);  var layer = new Layer(path, {    sensitive: this.caseSensitive,    strict: this.strict,    end: true  }, route.dispatch.bind(route));  layer.route = route;  this.stack.push(layer);  return route;};// create Router#VERB functionsmethods.concat('all').forEach(function(method){  proto[method] = function(path){    var route = this.route(path)    route[method].apply(route, slice.call(arguments, 1));    return this;  };});// append methods to a list of methodsfunction appendMethods(list, addition) {  for (var i = 0; i < addition.length; i++) {    var method = addition[i];    if (list.indexOf(method) === -1) {      list.push(method);    }  }}// get pathname of requestfunction getPathname(req) {  try {    return parseUrl(req).pathname;  } catch (err) {    return undefined;  }}// Get get protocol + host for a URLfunction getProtohost(url) {  if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {    return undefined  }  var searchIndex = url.indexOf('?')  var pathLength = searchIndex !== -1    ? searchIndex    : url.length  var fqdnIndex = url.slice(0, pathLength).indexOf('://')  return fqdnIndex !== -1    ? url.substring(0, url.indexOf('/', 3 + fqdnIndex))    : undefined}// get type for error messagefunction gettype(obj) {  var type = typeof obj;  if (type !== 'object') {    return type;  }  // inspect [[Class]] for objects  return toString.call(obj)    .replace(objectRegExp, '$1');}/** * Match path to a layer. * * @param {Layer} layer * @param {string} path * @private */function matchLayer(layer, path) {  try {    return layer.match(path);  } catch (err) {    return err;  }}// merge params with parent paramsfunction mergeParams(params, parent) {  if (typeof parent !== 'object' || !parent) {    return params;  }  // make copy of parent for base  var obj = mixin({}, parent);  // simple non-numeric merging  if (!(0 in params) || !(0 in parent)) {    return mixin(obj, params);  }  var i = 0;  var o = 0;  // determine numeric gaps  while (i in params) {    i++;  }  while (o in parent) {    o++;  }  // offset numeric indices in params before merge  for (i--; i >= 0; i--) {    params[i + o] = params[i];    // create holes for the merge when necessary    if (i < o) {      delete params[i];    }  }  return mixin(obj, params);}// restore obj props after functionfunction restore(fn, obj) {  var props = new Array(arguments.length - 2);  var vals = new Array(arguments.length - 2);  for (var i = 0; i < props.length; i++) {    props[i] = arguments[i + 2];    vals[i] = obj[props[i]];  }  return function () {    // restore vals    for (var i = 0; i < props.length; i++) {      obj[props[i]] = vals[i];    }    return fn.apply(this, arguments);  };}// send an OPTIONS responsefunction sendOptionsResponse(res, options, next) {  try {    var body = options.join(',');    res.set('Allow', body);    res.send(body);  } catch (err) {    next(err);  }}// wrap a functionfunction wrap(old, fn) {  return function proxy() {    var args = new Array(arguments.length + 1);    args[0] = old;    for (var i = 0, len = arguments.length; i < len; i++) {      args[i + 1] = arguments[i];    }    fn.apply(this, args);  };}
 |