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