second
[josuexyz/.git] / node_modules / express / lib / router / index.js
diff --git a/node_modules/express/lib/router/index.js b/node_modules/express/lib/router/index.js
new file mode 100644 (file)
index 0000000..69e6d38
--- /dev/null
@@ -0,0 +1,662 @@
+/*!
+ * 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 an 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.substr(1)) + ', fn) instead');
+    name = name.substr(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 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.substr(1);
+      slashAdded = false;
+    }
+
+    // restore altered req.url
+    if (removed.length !== 0) {
+      req.baseUrl = parentUrl;
+      req.url = protohost + removed + req.url.substr(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;
+    }
+
+    // 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;
+        continue;
+      }
+    }
+
+    // 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) {
+        return next(layerError || err);
+      }
+
+      if (route) {
+        return layer.handle_request(req, res, next);
+      }
+
+      trim_prefix(layer, layerError, layerPath, path);
+    });
+  }
+
+  function trim_prefix(layer, layerError, layerPath, path) {
+    if (layerPath.length !== 0) {
+      // 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.substr(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 functions
+methods.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 methods
+function 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 request
+function getPathname(req) {
+  try {
+    return parseUrl(req).pathname;
+  } catch (err) {
+    return undefined;
+  }
+}
+
+// Get get protocol + host for a URL
+function 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.substr(0, pathLength).indexOf('://')
+
+  return fqdnIndex !== -1
+    ? url.substr(0, url.indexOf('/', 3 + fqdnIndex))
+    : undefined
+}
+
+// get type for error message
+function 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 params
+function 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 function
+function 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 response
+function sendOptionsResponse(res, options, next) {
+  try {
+    var body = options.join(',');
+    res.set('Allow', body);
+    res.send(body);
+  } catch (err) {
+    next(err);
+  }
+}
+
+// wrap a function
+function 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);
+  };
+}