|
|
@@ -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 middleware functions');
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0; i < callbacks.length; i++) {
|
|
|
+ var fn = callbacks[i];
|
|
|
+
|
|
|
+ if (typeof fn !== 'function') {
|
|
|
+ throw new TypeError('Router.use() requires 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);
|
|
|
+ };
|
|
|
+}
|