annotate node_modules/express/lib/router/index.js @ 101:52e44ee1c791 tip master

enabled all scores in autostart script
author Rob Canning <rc@kiben.net>
date Tue, 21 Apr 2015 16:20:57 +0100
parents 0c3a2942ddee
children
rev   line source
rc@73 1 /**
rc@73 2 * Module dependencies.
rc@73 3 */
rc@73 4
rc@73 5 var Route = require('./route');
rc@73 6 var Layer = require('./layer');
rc@73 7 var methods = require('methods');
rc@73 8 var debug = require('debug')('express:router');
rc@73 9 var parseUrl = require('parseurl');
rc@73 10 var slice = Array.prototype.slice;
rc@73 11
rc@73 12 /**
rc@73 13 * Initialize a new `Router` with the given `options`.
rc@73 14 *
rc@73 15 * @param {Object} options
rc@73 16 * @return {Router} which is an callable function
rc@73 17 * @api public
rc@73 18 */
rc@73 19
rc@73 20 var proto = module.exports = function(options) {
rc@73 21 options = options || {};
rc@73 22
rc@73 23 function router(req, res, next) {
rc@73 24 router.handle(req, res, next);
rc@73 25 }
rc@73 26
rc@73 27 // mixin Router class functions
rc@73 28 router.__proto__ = proto;
rc@73 29
rc@73 30 router.params = {};
rc@73 31 router._params = [];
rc@73 32 router.caseSensitive = options.caseSensitive;
rc@73 33 router.strict = options.strict;
rc@73 34 router.stack = [];
rc@73 35
rc@73 36 return router;
rc@73 37 };
rc@73 38
rc@73 39 /**
rc@73 40 * Map the given param placeholder `name`(s) to the given callback.
rc@73 41 *
rc@73 42 * Parameter mapping is used to provide pre-conditions to routes
rc@73 43 * which use normalized placeholders. For example a _:user_id_ parameter
rc@73 44 * could automatically load a user's information from the database without
rc@73 45 * any additional code,
rc@73 46 *
rc@73 47 * The callback uses the same signature as middleware, the only difference
rc@73 48 * being that the value of the placeholder is passed, in this case the _id_
rc@73 49 * of the user. Once the `next()` function is invoked, just like middleware
rc@73 50 * it will continue on to execute the route, or subsequent parameter functions.
rc@73 51 *
rc@73 52 * Just like in middleware, you must either respond to the request or call next
rc@73 53 * to avoid stalling the request.
rc@73 54 *
rc@73 55 * app.param('user_id', function(req, res, next, id){
rc@73 56 * User.find(id, function(err, user){
rc@73 57 * if (err) {
rc@73 58 * return next(err);
rc@73 59 * } else if (!user) {
rc@73 60 * return next(new Error('failed to load user'));
rc@73 61 * }
rc@73 62 * req.user = user;
rc@73 63 * next();
rc@73 64 * });
rc@73 65 * });
rc@73 66 *
rc@73 67 * @param {String} name
rc@73 68 * @param {Function} fn
rc@73 69 * @return {app} for chaining
rc@73 70 * @api public
rc@73 71 */
rc@73 72
rc@73 73 proto.param = function(name, fn){
rc@73 74 // param logic
rc@73 75 if ('function' == typeof name) {
rc@73 76 this._params.push(name);
rc@73 77 return;
rc@73 78 }
rc@73 79
rc@73 80 // apply param functions
rc@73 81 var params = this._params;
rc@73 82 var len = params.length;
rc@73 83 var ret;
rc@73 84
rc@73 85 if (name[0] === ':') {
rc@73 86 name = name.substr(1);
rc@73 87 }
rc@73 88
rc@73 89 for (var i = 0; i < len; ++i) {
rc@73 90 if (ret = params[i](name, fn)) {
rc@73 91 fn = ret;
rc@73 92 }
rc@73 93 }
rc@73 94
rc@73 95 // ensure we end up with a
rc@73 96 // middleware function
rc@73 97 if ('function' != typeof fn) {
rc@73 98 throw new Error('invalid param() call for ' + name + ', got ' + fn);
rc@73 99 }
rc@73 100
rc@73 101 (this.params[name] = this.params[name] || []).push(fn);
rc@73 102 return this;
rc@73 103 };
rc@73 104
rc@73 105 /**
rc@73 106 * Dispatch a req, res into the router.
rc@73 107 *
rc@73 108 * @api private
rc@73 109 */
rc@73 110
rc@73 111 proto.handle = function(req, res, done) {
rc@73 112 var self = this;
rc@73 113
rc@73 114 debug('dispatching %s %s', req.method, req.url);
rc@73 115
rc@73 116 var method = req.method.toLowerCase();
rc@73 117
rc@73 118 var search = 1 + req.url.indexOf('?');
rc@73 119 var pathlength = search ? search - 1 : req.url.length;
rc@73 120 var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
rc@73 121 var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
rc@73 122 var idx = 0;
rc@73 123 var removed = '';
rc@73 124 var slashAdded = false;
rc@73 125 var paramcalled = {};
rc@73 126
rc@73 127 // store options for OPTIONS request
rc@73 128 // only used if OPTIONS request
rc@73 129 var options = [];
rc@73 130
rc@73 131 // middleware and routes
rc@73 132 var stack = self.stack;
rc@73 133
rc@73 134 // manage inter-router variables
rc@73 135 var parent = req.next;
rc@73 136 var parentUrl = req.baseUrl || '';
rc@73 137 done = wrap(done, function(old, err) {
rc@73 138 req.baseUrl = parentUrl;
rc@73 139 req.next = parent;
rc@73 140 old(err);
rc@73 141 });
rc@73 142 req.next = next;
rc@73 143
rc@73 144 // for options requests, respond with a default if nothing else responds
rc@73 145 if (method === 'options') {
rc@73 146 done = wrap(done, function(old, err) {
rc@73 147 if (err || options.length === 0) return old(err);
rc@73 148
rc@73 149 var body = options.join(',');
rc@73 150 return res.set('Allow', body).send(body);
rc@73 151 });
rc@73 152 }
rc@73 153
rc@73 154 next();
rc@73 155
rc@73 156 function next(err) {
rc@73 157 if (err === 'route') {
rc@73 158 err = undefined;
rc@73 159 }
rc@73 160
rc@73 161 var layer = stack[idx++];
rc@73 162 var layerPath;
rc@73 163
rc@73 164 if (!layer) {
rc@73 165 return done(err);
rc@73 166 }
rc@73 167
rc@73 168 if (slashAdded) {
rc@73 169 req.url = req.url.substr(1);
rc@73 170 slashAdded = false;
rc@73 171 }
rc@73 172
rc@73 173 req.baseUrl = parentUrl;
rc@73 174 req.url = protohost + removed + req.url.substr(protohost.length);
rc@73 175 req.originalUrl = req.originalUrl || req.url;
rc@73 176 removed = '';
rc@73 177
rc@73 178 try {
rc@73 179 var path = parseUrl(req).pathname;
rc@73 180 if (undefined == path) path = '/';
rc@73 181
rc@73 182 if (!layer.match(path)) return next(err);
rc@73 183
rc@73 184 // route object and not middleware
rc@73 185 var route = layer.route;
rc@73 186
rc@73 187 // if final route, then we support options
rc@73 188 if (route) {
rc@73 189 // we don't run any routes with error first
rc@73 190 if (err) {
rc@73 191 return next(err);
rc@73 192 }
rc@73 193
rc@73 194 req.route = route;
rc@73 195
rc@73 196 // we can now dispatch to the route
rc@73 197 if (method === 'options' && !route.methods['options']) {
rc@73 198 options.push.apply(options, route._options());
rc@73 199 }
rc@73 200 }
rc@73 201
rc@73 202 // Capture one-time layer values
rc@73 203 req.params = layer.params;
rc@73 204 layerPath = layer.path;
rc@73 205
rc@73 206 // this should be done for the layer
rc@73 207 return self.process_params(layer, paramcalled, req, res, function(err) {
rc@73 208 if (err) {
rc@73 209 return next(err);
rc@73 210 }
rc@73 211
rc@73 212 if (route) {
rc@73 213 return layer.handle(req, res, next);
rc@73 214 }
rc@73 215
rc@73 216 trim_prefix();
rc@73 217 });
rc@73 218
rc@73 219 } catch (err) {
rc@73 220 next(err);
rc@73 221 }
rc@73 222
rc@73 223 function trim_prefix() {
rc@73 224 var c = path[layerPath.length];
rc@73 225 if (c && '/' != c && '.' != c) return next(err);
rc@73 226
rc@73 227 // Trim off the part of the url that matches the route
rc@73 228 // middleware (.use stuff) needs to have the path stripped
rc@73 229 removed = layerPath;
rc@73 230 if (removed.length) {
rc@73 231 debug('trim prefix (%s) from url %s', layerPath, req.url);
rc@73 232 req.url = protohost + req.url.substr(protohost.length + removed.length);
rc@73 233 }
rc@73 234
rc@73 235 // Ensure leading slash
rc@73 236 if (!fqdn && req.url[0] !== '/') {
rc@73 237 req.url = '/' + req.url;
rc@73 238 slashAdded = true;
rc@73 239 }
rc@73 240
rc@73 241 // Setup base URL (no trailing slash)
rc@73 242 if (removed.length && removed.substr(-1) === '/') {
rc@73 243 req.baseUrl = parentUrl + removed.substring(0, removed.length - 1);
rc@73 244 } else {
rc@73 245 req.baseUrl = parentUrl + removed;
rc@73 246 }
rc@73 247
rc@73 248 debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl);
rc@73 249 var arity = layer.handle.length;
rc@73 250 try {
rc@73 251 if (err && arity === 4) {
rc@73 252 layer.handle(err, req, res, next);
rc@73 253 } else if (!err && arity < 4) {
rc@73 254 layer.handle(req, res, next);
rc@73 255 } else {
rc@73 256 next(err);
rc@73 257 }
rc@73 258 } catch (err) {
rc@73 259 next(err);
rc@73 260 }
rc@73 261 }
rc@73 262 }
rc@73 263
rc@73 264 function wrap(old, fn) {
rc@73 265 return function () {
rc@73 266 var args = [old].concat(slice.call(arguments));
rc@73 267 fn.apply(this, args);
rc@73 268 };
rc@73 269 }
rc@73 270 };
rc@73 271
rc@73 272 /**
rc@73 273 * Process any parameters for the layer.
rc@73 274 *
rc@73 275 * @api private
rc@73 276 */
rc@73 277
rc@73 278 proto.process_params = function(layer, called, req, res, done) {
rc@73 279 var params = this.params;
rc@73 280
rc@73 281 // captured parameters from the layer, keys and values
rc@73 282 var keys = layer.keys;
rc@73 283
rc@73 284 // fast track
rc@73 285 if (!keys || keys.length === 0) {
rc@73 286 return done();
rc@73 287 }
rc@73 288
rc@73 289 var i = 0;
rc@73 290 var name;
rc@73 291 var paramIndex = 0;
rc@73 292 var key;
rc@73 293 var paramVal;
rc@73 294 var paramCallbacks;
rc@73 295 var paramCalled;
rc@73 296
rc@73 297 // process params in order
rc@73 298 // param callbacks can be async
rc@73 299 function param(err) {
rc@73 300 if (err) {
rc@73 301 return done(err);
rc@73 302 }
rc@73 303
rc@73 304 if (i >= keys.length ) {
rc@73 305 return done();
rc@73 306 }
rc@73 307
rc@73 308 paramIndex = 0;
rc@73 309 key = keys[i++];
rc@73 310
rc@73 311 if (!key) {
rc@73 312 return done();
rc@73 313 }
rc@73 314
rc@73 315 name = key.name;
rc@73 316 paramVal = req.params[name];
rc@73 317 paramCallbacks = params[name];
rc@73 318 paramCalled = called[name];
rc@73 319
rc@73 320 if (paramVal === undefined || !paramCallbacks) {
rc@73 321 return param();
rc@73 322 }
rc@73 323
rc@73 324 // param previously called with same value or error occurred
rc@73 325 if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) {
rc@73 326 // restore value
rc@73 327 req.params[name] = paramCalled.value;
rc@73 328
rc@73 329 // next param
rc@73 330 return param(paramCalled.error);
rc@73 331 }
rc@73 332
rc@73 333 called[name] = paramCalled = {
rc@73 334 error: null,
rc@73 335 match: paramVal,
rc@73 336 value: paramVal
rc@73 337 };
rc@73 338
rc@73 339 try {
rc@73 340 return paramCallback();
rc@73 341 } catch (err) {
rc@73 342 return done(err);
rc@73 343 }
rc@73 344 }
rc@73 345
rc@73 346 // single param callbacks
rc@73 347 function paramCallback(err) {
rc@73 348 var fn = paramCallbacks[paramIndex++];
rc@73 349
rc@73 350 // store updated value
rc@73 351 paramCalled.value = req.params[key.name];
rc@73 352
rc@73 353 if (err) {
rc@73 354 // store error
rc@73 355 paramCalled.error = err;
rc@73 356 param(err);
rc@73 357 return;
rc@73 358 }
rc@73 359
rc@73 360 if (!fn) return param();
rc@73 361
rc@73 362 fn(req, res, paramCallback, paramVal, key.name);
rc@73 363 }
rc@73 364
rc@73 365 param();
rc@73 366 };
rc@73 367
rc@73 368 /**
rc@73 369 * Use the given middleware function, with optional path, defaulting to "/".
rc@73 370 *
rc@73 371 * Use (like `.all`) will run for any http METHOD, but it will not add
rc@73 372 * handlers for those methods so OPTIONS requests will not consider `.use`
rc@73 373 * functions even if they could respond.
rc@73 374 *
rc@73 375 * The other difference is that _route_ path is stripped and not visible
rc@73 376 * to the handler function. The main effect of this feature is that mounted
rc@73 377 * handlers can operate without any code changes regardless of the "prefix"
rc@73 378 * pathname.
rc@73 379 *
rc@73 380 * @param {String|Function} route
rc@73 381 * @param {Function} fn
rc@73 382 * @return {app} for chaining
rc@73 383 * @api public
rc@73 384 */
rc@73 385
rc@73 386 proto.use = function(route, fn){
rc@73 387 // default route to '/'
rc@73 388 if ('string' != typeof route) {
rc@73 389 fn = route;
rc@73 390 route = '/';
rc@73 391 }
rc@73 392
rc@73 393 if (typeof fn !== 'function') {
rc@73 394 var type = {}.toString.call(fn);
rc@73 395 var msg = 'Router.use() requires callback functions but got a ' + type;
rc@73 396 throw new Error(msg);
rc@73 397 }
rc@73 398
rc@73 399 // strip trailing slash
rc@73 400 if ('/' == route[route.length - 1]) {
rc@73 401 route = route.slice(0, -1);
rc@73 402 }
rc@73 403
rc@73 404 var layer = new Layer(route, {
rc@73 405 sensitive: this.caseSensitive,
rc@73 406 strict: this.strict,
rc@73 407 end: false
rc@73 408 }, fn);
rc@73 409
rc@73 410 // add the middleware
rc@73 411 debug('use %s %s', route || '/', fn.name || 'anonymous');
rc@73 412
rc@73 413 this.stack.push(layer);
rc@73 414 return this;
rc@73 415 };
rc@73 416
rc@73 417 /**
rc@73 418 * Create a new Route for the given path.
rc@73 419 *
rc@73 420 * Each route contains a separate middleware stack and VERB handlers.
rc@73 421 *
rc@73 422 * See the Route api documentation for details on adding handlers
rc@73 423 * and middleware to routes.
rc@73 424 *
rc@73 425 * @param {String} path
rc@73 426 * @return {Route}
rc@73 427 * @api public
rc@73 428 */
rc@73 429
rc@73 430 proto.route = function(path){
rc@73 431 var route = new Route(path);
rc@73 432
rc@73 433 var layer = new Layer(path, {
rc@73 434 sensitive: this.caseSensitive,
rc@73 435 strict: this.strict,
rc@73 436 end: true
rc@73 437 }, route.dispatch.bind(route));
rc@73 438
rc@73 439 layer.route = route;
rc@73 440
rc@73 441 this.stack.push(layer);
rc@73 442 return route;
rc@73 443 };
rc@73 444
rc@73 445 // create Router#VERB functions
rc@73 446 methods.concat('all').forEach(function(method){
rc@73 447 proto[method] = function(path){
rc@73 448 var route = this.route(path)
rc@73 449 route[method].apply(route, [].slice.call(arguments, 1));
rc@73 450 return this;
rc@73 451 };
rc@73 452 });