mirror of
				https://github.com/KevinMidboe/zoff.git
				synced 2025-10-29 18:00:23 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			577 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			577 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
 | 
						|
/**
 | 
						|
 * Module dependencies.
 | 
						|
 */
 | 
						|
 | 
						|
var Route = require('./route');
 | 
						|
var Layer = require('./layer');
 | 
						|
var methods = require('methods');
 | 
						|
var mixin = require('utils-merge');
 | 
						|
var debug = require('debug')('express:router');
 | 
						|
var parseUrl = require('parseurl');
 | 
						|
var utils = require('../utils');
 | 
						|
 | 
						|
/**
 | 
						|
 * Module variables.
 | 
						|
 */
 | 
						|
 | 
						|
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
 | 
						|
 * @api public
 | 
						|
 */
 | 
						|
 | 
						|
var proto = module.exports = function(options) {
 | 
						|
  options = options || {};
 | 
						|
 | 
						|
  function router(req, res, next) {
 | 
						|
    router.handle(req, res, next);
 | 
						|
  }
 | 
						|
 | 
						|
  // mixin Router class functions
 | 
						|
  router.__proto__ = proto;
 | 
						|
 | 
						|
  router.params = {};
 | 
						|
  router._params = [];
 | 
						|
  router.caseSensitive = options.caseSensitive;
 | 
						|
  router.mergeParams = options.mergeParams;
 | 
						|
  router.strict = options.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
 | 
						|
 * @api public
 | 
						|
 */
 | 
						|
 | 
						|
proto.param = function(name, fn){
 | 
						|
  // param logic
 | 
						|
  if ('function' == typeof name) {
 | 
						|
    this._params.push(name);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // apply param functions
 | 
						|
  var params = this._params;
 | 
						|
  var len = params.length;
 | 
						|
  var ret;
 | 
						|
 | 
						|
  if (name[0] === ':') {
 | 
						|
    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.
 | 
						|
 *
 | 
						|
 * @api private
 | 
						|
 */
 | 
						|
 | 
						|
proto.handle = function(req, res, done) {
 | 
						|
  var self = this;
 | 
						|
 | 
						|
  debug('dispatching %s %s', req.method, req.url);
 | 
						|
 | 
						|
  var search = 1 + req.url.indexOf('?');
 | 
						|
  var pathlength = search ? search - 1 : req.url.length;
 | 
						|
  var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
 | 
						|
  var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
 | 
						|
  var idx = 0;
 | 
						|
  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 || '';
 | 
						|
  done = restore(done, 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);
 | 
						|
 | 
						|
      var body = options.join(',');
 | 
						|
      return res.set('Allow', body).send(body);
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  // setup basic req values
 | 
						|
  req.baseUrl = parentUrl;
 | 
						|
  req.originalUrl = req.originalUrl || req.url;
 | 
						|
 | 
						|
  next();
 | 
						|
 | 
						|
  function next(err) {
 | 
						|
    var layerError = err === 'route'
 | 
						|
      ? null
 | 
						|
      : err;
 | 
						|
 | 
						|
    var layer = stack[idx++];
 | 
						|
 | 
						|
    if (slashAdded) {
 | 
						|
      req.url = req.url.substr(1);
 | 
						|
      slashAdded = false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (removed.length !== 0) {
 | 
						|
      req.baseUrl = parentUrl;
 | 
						|
      req.url = protohost + removed + req.url.substr(protohost.length);
 | 
						|
      removed = '';
 | 
						|
    }
 | 
						|
 | 
						|
    if (!layer) {
 | 
						|
      setImmediate(done, layerError);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    self.match_layer(layer, req, res, function (err, path) {
 | 
						|
      if (err || path === undefined) {
 | 
						|
        return next(layerError || err);
 | 
						|
      }
 | 
						|
 | 
						|
      // route object and not middleware
 | 
						|
      var route = layer.route;
 | 
						|
 | 
						|
      // if final route, then we support options
 | 
						|
      if (route) {
 | 
						|
        // we don't run any routes with error first
 | 
						|
        if (layerError) {
 | 
						|
          return next(layerError);
 | 
						|
        }
 | 
						|
 | 
						|
        var method = req.method;
 | 
						|
        var has_method = route._handles_method(method);
 | 
						|
 | 
						|
        // build up automatic options response
 | 
						|
        if (!has_method && method === 'OPTIONS') {
 | 
						|
          options.push.apply(options, route._options());
 | 
						|
        }
 | 
						|
 | 
						|
        // don't even bother
 | 
						|
        if (!has_method && method !== 'HEAD') {
 | 
						|
          return next();
 | 
						|
        }
 | 
						|
 | 
						|
        // we can now dispatch to the 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) {
 | 
						|
    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
 | 
						|
    if (layerPath.length !== 0) {
 | 
						|
      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 (!fqdn && 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);
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Match request to a layer.
 | 
						|
 *
 | 
						|
 * @api private
 | 
						|
 */
 | 
						|
 | 
						|
proto.match_layer = function match_layer(layer, req, res, done) {
 | 
						|
  var error = null;
 | 
						|
  var path;
 | 
						|
 | 
						|
  try {
 | 
						|
    path = parseUrl(req).pathname;
 | 
						|
 | 
						|
    if (!layer.match(path)) {
 | 
						|
      path = undefined;
 | 
						|
    }
 | 
						|
  } catch (err) {
 | 
						|
    error = err;
 | 
						|
  }
 | 
						|
 | 
						|
  done(error, path);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Process any parameters for the layer.
 | 
						|
 *
 | 
						|
 * @api private
 | 
						|
 */
 | 
						|
 | 
						|
proto.process_params = function(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++];
 | 
						|
 | 
						|
    if (!key) {
 | 
						|
      return done();
 | 
						|
    }
 | 
						|
 | 
						|
    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.error || paramCalled.match === paramVal)) {
 | 
						|
      // 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.
 | 
						|
 *
 | 
						|
 * @api 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 = utils.flatten(slice.call(arguments, offset));
 | 
						|
 | 
						|
  if (callbacks.length === 0) {
 | 
						|
    throw new TypeError('Router.use() requires middleware functions');
 | 
						|
  }
 | 
						|
 | 
						|
  callbacks.forEach(function (fn) {
 | 
						|
    if (typeof fn !== 'function') {
 | 
						|
      throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
 | 
						|
    }
 | 
						|
 | 
						|
    // add the middleware
 | 
						|
    debug('use %s %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);
 | 
						|
  }, this);
 | 
						|
 | 
						|
  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}
 | 
						|
 * @api public
 | 
						|
 */
 | 
						|
 | 
						|
proto.route = function(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;
 | 
						|
  };
 | 
						|
});
 | 
						|
 | 
						|
// 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');
 | 
						|
}
 | 
						|
 | 
						|
// 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 === o || o in parent) {
 | 
						|
    if (i in params) i++;
 | 
						|
    if (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(parent, 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(err){
 | 
						|
    // restore vals
 | 
						|
    for (var i = 0; i < props.length; i++) {
 | 
						|
      obj[props[i]] = vals[i];
 | 
						|
    }
 | 
						|
 | 
						|
    return fn.apply(this, arguments);
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
// 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);
 | 
						|
  };
 | 
						|
}
 |