663 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			663 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
|  * 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);
 | |
|   };
 | |
| }
 |