twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

index.js (14883B)


      1 /*!
      2  * express
      3  * Copyright(c) 2009-2013 TJ Holowaychuk
      4  * Copyright(c) 2013 Roman Shtylman
      5  * Copyright(c) 2014-2015 Douglas Christopher Wilson
      6  * MIT Licensed
      7  */
      8 
      9 'use strict';
     10 
     11 /**
     12  * Module dependencies.
     13  * @private
     14  */
     15 
     16 var Route = require('./route');
     17 var Layer = require('./layer');
     18 var methods = require('methods');
     19 var mixin = require('utils-merge');
     20 var debug = require('debug')('express:router');
     21 var deprecate = require('depd')('express');
     22 var flatten = require('array-flatten');
     23 var parseUrl = require('parseurl');
     24 var setPrototypeOf = require('setprototypeof')
     25 
     26 /**
     27  * Module variables.
     28  * @private
     29  */
     30 
     31 var objectRegExp = /^\[object (\S+)\]$/;
     32 var slice = Array.prototype.slice;
     33 var toString = Object.prototype.toString;
     34 
     35 /**
     36  * Initialize a new `Router` with the given `options`.
     37  *
     38  * @param {Object} [options]
     39  * @return {Router} which is an callable function
     40  * @public
     41  */
     42 
     43 var proto = module.exports = function(options) {
     44   var opts = options || {};
     45 
     46   function router(req, res, next) {
     47     router.handle(req, res, next);
     48   }
     49 
     50   // mixin Router class functions
     51   setPrototypeOf(router, proto)
     52 
     53   router.params = {};
     54   router._params = [];
     55   router.caseSensitive = opts.caseSensitive;
     56   router.mergeParams = opts.mergeParams;
     57   router.strict = opts.strict;
     58   router.stack = [];
     59 
     60   return router;
     61 };
     62 
     63 /**
     64  * Map the given param placeholder `name`(s) to the given callback.
     65  *
     66  * Parameter mapping is used to provide pre-conditions to routes
     67  * which use normalized placeholders. For example a _:user_id_ parameter
     68  * could automatically load a user's information from the database without
     69  * any additional code,
     70  *
     71  * The callback uses the same signature as middleware, the only difference
     72  * being that the value of the placeholder is passed, in this case the _id_
     73  * of the user. Once the `next()` function is invoked, just like middleware
     74  * it will continue on to execute the route, or subsequent parameter functions.
     75  *
     76  * Just like in middleware, you must either respond to the request or call next
     77  * to avoid stalling the request.
     78  *
     79  *  app.param('user_id', function(req, res, next, id){
     80  *    User.find(id, function(err, user){
     81  *      if (err) {
     82  *        return next(err);
     83  *      } else if (!user) {
     84  *        return next(new Error('failed to load user'));
     85  *      }
     86  *      req.user = user;
     87  *      next();
     88  *    });
     89  *  });
     90  *
     91  * @param {String} name
     92  * @param {Function} fn
     93  * @return {app} for chaining
     94  * @public
     95  */
     96 
     97 proto.param = function param(name, fn) {
     98   // param logic
     99   if (typeof name === 'function') {
    100     deprecate('router.param(fn): Refactor to use path params');
    101     this._params.push(name);
    102     return;
    103   }
    104 
    105   // apply param functions
    106   var params = this._params;
    107   var len = params.length;
    108   var ret;
    109 
    110   if (name[0] === ':') {
    111     deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');
    112     name = name.substr(1);
    113   }
    114 
    115   for (var i = 0; i < len; ++i) {
    116     if (ret = params[i](name, fn)) {
    117       fn = ret;
    118     }
    119   }
    120 
    121   // ensure we end up with a
    122   // middleware function
    123   if ('function' !== typeof fn) {
    124     throw new Error('invalid param() call for ' + name + ', got ' + fn);
    125   }
    126 
    127   (this.params[name] = this.params[name] || []).push(fn);
    128   return this;
    129 };
    130 
    131 /**
    132  * Dispatch a req, res into the router.
    133  * @private
    134  */
    135 
    136 proto.handle = function handle(req, res, out) {
    137   var self = this;
    138 
    139   debug('dispatching %s %s', req.method, req.url);
    140 
    141   var idx = 0;
    142   var protohost = getProtohost(req.url) || ''
    143   var removed = '';
    144   var slashAdded = false;
    145   var paramcalled = {};
    146 
    147   // store options for OPTIONS request
    148   // only used if OPTIONS request
    149   var options = [];
    150 
    151   // middleware and routes
    152   var stack = self.stack;
    153 
    154   // manage inter-router variables
    155   var parentParams = req.params;
    156   var parentUrl = req.baseUrl || '';
    157   var done = restore(out, req, 'baseUrl', 'next', 'params');
    158 
    159   // setup next layer
    160   req.next = next;
    161 
    162   // for options requests, respond with a default if nothing else responds
    163   if (req.method === 'OPTIONS') {
    164     done = wrap(done, function(old, err) {
    165       if (err || options.length === 0) return old(err);
    166       sendOptionsResponse(res, options, old);
    167     });
    168   }
    169 
    170   // setup basic req values
    171   req.baseUrl = parentUrl;
    172   req.originalUrl = req.originalUrl || req.url;
    173 
    174   next();
    175 
    176   function next(err) {
    177     var layerError = err === 'route'
    178       ? null
    179       : err;
    180 
    181     // remove added slash
    182     if (slashAdded) {
    183       req.url = req.url.substr(1);
    184       slashAdded = false;
    185     }
    186 
    187     // restore altered req.url
    188     if (removed.length !== 0) {
    189       req.baseUrl = parentUrl;
    190       req.url = protohost + removed + req.url.substr(protohost.length);
    191       removed = '';
    192     }
    193 
    194     // signal to exit router
    195     if (layerError === 'router') {
    196       setImmediate(done, null)
    197       return
    198     }
    199 
    200     // no more matching layers
    201     if (idx >= stack.length) {
    202       setImmediate(done, layerError);
    203       return;
    204     }
    205 
    206     // get pathname of request
    207     var path = getPathname(req);
    208 
    209     if (path == null) {
    210       return done(layerError);
    211     }
    212 
    213     // find next matching layer
    214     var layer;
    215     var match;
    216     var route;
    217 
    218     while (match !== true && idx < stack.length) {
    219       layer = stack[idx++];
    220       match = matchLayer(layer, path);
    221       route = layer.route;
    222 
    223       if (typeof match !== 'boolean') {
    224         // hold on to layerError
    225         layerError = layerError || match;
    226       }
    227 
    228       if (match !== true) {
    229         continue;
    230       }
    231 
    232       if (!route) {
    233         // process non-route handlers normally
    234         continue;
    235       }
    236 
    237       if (layerError) {
    238         // routes do not match with a pending error
    239         match = false;
    240         continue;
    241       }
    242 
    243       var method = req.method;
    244       var has_method = route._handles_method(method);
    245 
    246       // build up automatic options response
    247       if (!has_method && method === 'OPTIONS') {
    248         appendMethods(options, route._options());
    249       }
    250 
    251       // don't even bother matching route
    252       if (!has_method && method !== 'HEAD') {
    253         match = false;
    254         continue;
    255       }
    256     }
    257 
    258     // no match
    259     if (match !== true) {
    260       return done(layerError);
    261     }
    262 
    263     // store route for dispatch on change
    264     if (route) {
    265       req.route = route;
    266     }
    267 
    268     // Capture one-time layer values
    269     req.params = self.mergeParams
    270       ? mergeParams(layer.params, parentParams)
    271       : layer.params;
    272     var layerPath = layer.path;
    273 
    274     // this should be done for the layer
    275     self.process_params(layer, paramcalled, req, res, function (err) {
    276       if (err) {
    277         return next(layerError || err);
    278       }
    279 
    280       if (route) {
    281         return layer.handle_request(req, res, next);
    282       }
    283 
    284       trim_prefix(layer, layerError, layerPath, path);
    285     });
    286   }
    287 
    288   function trim_prefix(layer, layerError, layerPath, path) {
    289     if (layerPath.length !== 0) {
    290       // Validate path breaks on a path separator
    291       var c = path[layerPath.length]
    292       if (c && c !== '/' && c !== '.') return next(layerError)
    293 
    294       // Trim off the part of the url that matches the route
    295       // middleware (.use stuff) needs to have the path stripped
    296       debug('trim prefix (%s) from url %s', layerPath, req.url);
    297       removed = layerPath;
    298       req.url = protohost + req.url.substr(protohost.length + removed.length);
    299 
    300       // Ensure leading slash
    301       if (!protohost && req.url[0] !== '/') {
    302         req.url = '/' + req.url;
    303         slashAdded = true;
    304       }
    305 
    306       // Setup base URL (no trailing slash)
    307       req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
    308         ? removed.substring(0, removed.length - 1)
    309         : removed);
    310     }
    311 
    312     debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
    313 
    314     if (layerError) {
    315       layer.handle_error(layerError, req, res, next);
    316     } else {
    317       layer.handle_request(req, res, next);
    318     }
    319   }
    320 };
    321 
    322 /**
    323  * Process any parameters for the layer.
    324  * @private
    325  */
    326 
    327 proto.process_params = function process_params(layer, called, req, res, done) {
    328   var params = this.params;
    329 
    330   // captured parameters from the layer, keys and values
    331   var keys = layer.keys;
    332 
    333   // fast track
    334   if (!keys || keys.length === 0) {
    335     return done();
    336   }
    337 
    338   var i = 0;
    339   var name;
    340   var paramIndex = 0;
    341   var key;
    342   var paramVal;
    343   var paramCallbacks;
    344   var paramCalled;
    345 
    346   // process params in order
    347   // param callbacks can be async
    348   function param(err) {
    349     if (err) {
    350       return done(err);
    351     }
    352 
    353     if (i >= keys.length ) {
    354       return done();
    355     }
    356 
    357     paramIndex = 0;
    358     key = keys[i++];
    359     name = key.name;
    360     paramVal = req.params[name];
    361     paramCallbacks = params[name];
    362     paramCalled = called[name];
    363 
    364     if (paramVal === undefined || !paramCallbacks) {
    365       return param();
    366     }
    367 
    368     // param previously called with same value or error occurred
    369     if (paramCalled && (paramCalled.match === paramVal
    370       || (paramCalled.error && paramCalled.error !== 'route'))) {
    371       // restore value
    372       req.params[name] = paramCalled.value;
    373 
    374       // next param
    375       return param(paramCalled.error);
    376     }
    377 
    378     called[name] = paramCalled = {
    379       error: null,
    380       match: paramVal,
    381       value: paramVal
    382     };
    383 
    384     paramCallback();
    385   }
    386 
    387   // single param callbacks
    388   function paramCallback(err) {
    389     var fn = paramCallbacks[paramIndex++];
    390 
    391     // store updated value
    392     paramCalled.value = req.params[key.name];
    393 
    394     if (err) {
    395       // store error
    396       paramCalled.error = err;
    397       param(err);
    398       return;
    399     }
    400 
    401     if (!fn) return param();
    402 
    403     try {
    404       fn(req, res, paramCallback, paramVal, key.name);
    405     } catch (e) {
    406       paramCallback(e);
    407     }
    408   }
    409 
    410   param();
    411 };
    412 
    413 /**
    414  * Use the given middleware function, with optional path, defaulting to "/".
    415  *
    416  * Use (like `.all`) will run for any http METHOD, but it will not add
    417  * handlers for those methods so OPTIONS requests will not consider `.use`
    418  * functions even if they could respond.
    419  *
    420  * The other difference is that _route_ path is stripped and not visible
    421  * to the handler function. The main effect of this feature is that mounted
    422  * handlers can operate without any code changes regardless of the "prefix"
    423  * pathname.
    424  *
    425  * @public
    426  */
    427 
    428 proto.use = function use(fn) {
    429   var offset = 0;
    430   var path = '/';
    431 
    432   // default path to '/'
    433   // disambiguate router.use([fn])
    434   if (typeof fn !== 'function') {
    435     var arg = fn;
    436 
    437     while (Array.isArray(arg) && arg.length !== 0) {
    438       arg = arg[0];
    439     }
    440 
    441     // first arg is the path
    442     if (typeof arg !== 'function') {
    443       offset = 1;
    444       path = fn;
    445     }
    446   }
    447 
    448   var callbacks = flatten(slice.call(arguments, offset));
    449 
    450   if (callbacks.length === 0) {
    451     throw new TypeError('Router.use() requires a middleware function')
    452   }
    453 
    454   for (var i = 0; i < callbacks.length; i++) {
    455     var fn = callbacks[i];
    456 
    457     if (typeof fn !== 'function') {
    458       throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
    459     }
    460 
    461     // add the middleware
    462     debug('use %o %s', path, fn.name || '<anonymous>')
    463 
    464     var layer = new Layer(path, {
    465       sensitive: this.caseSensitive,
    466       strict: false,
    467       end: false
    468     }, fn);
    469 
    470     layer.route = undefined;
    471 
    472     this.stack.push(layer);
    473   }
    474 
    475   return this;
    476 };
    477 
    478 /**
    479  * Create a new Route for the given path.
    480  *
    481  * Each route contains a separate middleware stack and VERB handlers.
    482  *
    483  * See the Route api documentation for details on adding handlers
    484  * and middleware to routes.
    485  *
    486  * @param {String} path
    487  * @return {Route}
    488  * @public
    489  */
    490 
    491 proto.route = function route(path) {
    492   var route = new Route(path);
    493 
    494   var layer = new Layer(path, {
    495     sensitive: this.caseSensitive,
    496     strict: this.strict,
    497     end: true
    498   }, route.dispatch.bind(route));
    499 
    500   layer.route = route;
    501 
    502   this.stack.push(layer);
    503   return route;
    504 };
    505 
    506 // create Router#VERB functions
    507 methods.concat('all').forEach(function(method){
    508   proto[method] = function(path){
    509     var route = this.route(path)
    510     route[method].apply(route, slice.call(arguments, 1));
    511     return this;
    512   };
    513 });
    514 
    515 // append methods to a list of methods
    516 function appendMethods(list, addition) {
    517   for (var i = 0; i < addition.length; i++) {
    518     var method = addition[i];
    519     if (list.indexOf(method) === -1) {
    520       list.push(method);
    521     }
    522   }
    523 }
    524 
    525 // get pathname of request
    526 function getPathname(req) {
    527   try {
    528     return parseUrl(req).pathname;
    529   } catch (err) {
    530     return undefined;
    531   }
    532 }
    533 
    534 // Get get protocol + host for a URL
    535 function getProtohost(url) {
    536   if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
    537     return undefined
    538   }
    539 
    540   var searchIndex = url.indexOf('?')
    541   var pathLength = searchIndex !== -1
    542     ? searchIndex
    543     : url.length
    544   var fqdnIndex = url.substr(0, pathLength).indexOf('://')
    545 
    546   return fqdnIndex !== -1
    547     ? url.substr(0, url.indexOf('/', 3 + fqdnIndex))
    548     : undefined
    549 }
    550 
    551 // get type for error message
    552 function gettype(obj) {
    553   var type = typeof obj;
    554 
    555   if (type !== 'object') {
    556     return type;
    557   }
    558 
    559   // inspect [[Class]] for objects
    560   return toString.call(obj)
    561     .replace(objectRegExp, '$1');
    562 }
    563 
    564 /**
    565  * Match path to a layer.
    566  *
    567  * @param {Layer} layer
    568  * @param {string} path
    569  * @private
    570  */
    571 
    572 function matchLayer(layer, path) {
    573   try {
    574     return layer.match(path);
    575   } catch (err) {
    576     return err;
    577   }
    578 }
    579 
    580 // merge params with parent params
    581 function mergeParams(params, parent) {
    582   if (typeof parent !== 'object' || !parent) {
    583     return params;
    584   }
    585 
    586   // make copy of parent for base
    587   var obj = mixin({}, parent);
    588 
    589   // simple non-numeric merging
    590   if (!(0 in params) || !(0 in parent)) {
    591     return mixin(obj, params);
    592   }
    593 
    594   var i = 0;
    595   var o = 0;
    596 
    597   // determine numeric gaps
    598   while (i in params) {
    599     i++;
    600   }
    601 
    602   while (o in parent) {
    603     o++;
    604   }
    605 
    606   // offset numeric indices in params before merge
    607   for (i--; i >= 0; i--) {
    608     params[i + o] = params[i];
    609 
    610     // create holes for the merge when necessary
    611     if (i < o) {
    612       delete params[i];
    613     }
    614   }
    615 
    616   return mixin(obj, params);
    617 }
    618 
    619 // restore obj props after function
    620 function restore(fn, obj) {
    621   var props = new Array(arguments.length - 2);
    622   var vals = new Array(arguments.length - 2);
    623 
    624   for (var i = 0; i < props.length; i++) {
    625     props[i] = arguments[i + 2];
    626     vals[i] = obj[props[i]];
    627   }
    628 
    629   return function () {
    630     // restore vals
    631     for (var i = 0; i < props.length; i++) {
    632       obj[props[i]] = vals[i];
    633     }
    634 
    635     return fn.apply(this, arguments);
    636   };
    637 }
    638 
    639 // send an OPTIONS response
    640 function sendOptionsResponse(res, options, next) {
    641   try {
    642     var body = options.join(',');
    643     res.set('Allow', body);
    644     res.send(body);
    645   } catch (err) {
    646     next(err);
    647   }
    648 }
    649 
    650 // wrap a function
    651 function wrap(old, fn) {
    652   return function proxy() {
    653     var args = new Array(arguments.length + 1);
    654 
    655     args[0] = old;
    656     for (var i = 0, len = arguments.length; i < len; i++) {
    657       args[i + 1] = arguments[i];
    658     }
    659 
    660     fn.apply(this, args);
    661   };
    662 }