source: node_modules/express/lib/router/index.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 14.8 KB
RevLine 
[d24f17c]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
16var Route = require('./route');
17var Layer = require('./layer');
18var methods = require('methods');
19var mixin = require('utils-merge');
20var debug = require('debug')('express:router');
21var deprecate = require('depd')('express');
22var flatten = require('array-flatten');
23var parseUrl = require('parseurl');
24var setPrototypeOf = require('setprototypeof')
25
26/**
27 * Module variables.
28 * @private
29 */
30
31var objectRegExp = /^\[object (\S+)\]$/;
32var slice = Array.prototype.slice;
33var 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
43var 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
97proto.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.slice(1)) + ', fn) instead')
112 name = name.slice(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
136proto.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 sync = 0
146 var paramcalled = {};
147
148 // store options for OPTIONS request
149 // only used if OPTIONS request
150 var options = [];
151
152 // middleware and routes
153 var stack = self.stack;
154
155 // manage inter-router variables
156 var parentParams = req.params;
157 var parentUrl = req.baseUrl || '';
158 var done = restore(out, req, 'baseUrl', 'next', 'params');
159
160 // setup next layer
161 req.next = next;
162
163 // for options requests, respond with a default if nothing else responds
164 if (req.method === 'OPTIONS') {
165 done = wrap(done, function(old, err) {
166 if (err || options.length === 0) return old(err);
167 sendOptionsResponse(res, options, old);
168 });
169 }
170
171 // setup basic req values
172 req.baseUrl = parentUrl;
173 req.originalUrl = req.originalUrl || req.url;
174
175 next();
176
177 function next(err) {
178 var layerError = err === 'route'
179 ? null
180 : err;
181
182 // remove added slash
183 if (slashAdded) {
184 req.url = req.url.slice(1)
185 slashAdded = false;
186 }
187
188 // restore altered req.url
189 if (removed.length !== 0) {
190 req.baseUrl = parentUrl;
191 req.url = protohost + removed + req.url.slice(protohost.length)
192 removed = '';
193 }
194
195 // signal to exit router
196 if (layerError === 'router') {
197 setImmediate(done, null)
198 return
199 }
200
201 // no more matching layers
202 if (idx >= stack.length) {
203 setImmediate(done, layerError);
204 return;
205 }
206
207 // max sync stack
208 if (++sync > 100) {
209 return setImmediate(next, err)
210 }
211
212 // get pathname of request
213 var path = getPathname(req);
214
215 if (path == null) {
216 return done(layerError);
217 }
218
219 // find next matching layer
220 var layer;
221 var match;
222 var route;
223
224 while (match !== true && idx < stack.length) {
225 layer = stack[idx++];
226 match = matchLayer(layer, path);
227 route = layer.route;
228
229 if (typeof match !== 'boolean') {
230 // hold on to layerError
231 layerError = layerError || match;
232 }
233
234 if (match !== true) {
235 continue;
236 }
237
238 if (!route) {
239 // process non-route handlers normally
240 continue;
241 }
242
243 if (layerError) {
244 // routes do not match with a pending error
245 match = false;
246 continue;
247 }
248
249 var method = req.method;
250 var has_method = route._handles_method(method);
251
252 // build up automatic options response
253 if (!has_method && method === 'OPTIONS') {
254 appendMethods(options, route._options());
255 }
256
257 // don't even bother matching route
258 if (!has_method && method !== 'HEAD') {
259 match = false;
260 }
261 }
262
263 // no match
264 if (match !== true) {
265 return done(layerError);
266 }
267
268 // store route for dispatch on change
269 if (route) {
270 req.route = route;
271 }
272
273 // Capture one-time layer values
274 req.params = self.mergeParams
275 ? mergeParams(layer.params, parentParams)
276 : layer.params;
277 var layerPath = layer.path;
278
279 // this should be done for the layer
280 self.process_params(layer, paramcalled, req, res, function (err) {
281 if (err) {
282 next(layerError || err)
283 } else if (route) {
284 layer.handle_request(req, res, next)
285 } else {
286 trim_prefix(layer, layerError, layerPath, path)
287 }
288
289 sync = 0
290 });
291 }
292
293 function trim_prefix(layer, layerError, layerPath, path) {
294 if (layerPath.length !== 0) {
295 // Validate path is a prefix match
296 if (layerPath !== path.slice(0, layerPath.length)) {
297 next(layerError)
298 return
299 }
300
301 // Validate path breaks on a path separator
302 var c = path[layerPath.length]
303 if (c && c !== '/' && c !== '.') return next(layerError)
304
305 // Trim off the part of the url that matches the route
306 // middleware (.use stuff) needs to have the path stripped
307 debug('trim prefix (%s) from url %s', layerPath, req.url);
308 removed = layerPath;
309 req.url = protohost + req.url.slice(protohost.length + removed.length)
310
311 // Ensure leading slash
312 if (!protohost && req.url[0] !== '/') {
313 req.url = '/' + req.url;
314 slashAdded = true;
315 }
316
317 // Setup base URL (no trailing slash)
318 req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
319 ? removed.substring(0, removed.length - 1)
320 : removed);
321 }
322
323 debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
324
325 if (layerError) {
326 layer.handle_error(layerError, req, res, next);
327 } else {
328 layer.handle_request(req, res, next);
329 }
330 }
331};
332
333/**
334 * Process any parameters for the layer.
335 * @private
336 */
337
338proto.process_params = function process_params(layer, called, req, res, done) {
339 var params = this.params;
340
341 // captured parameters from the layer, keys and values
342 var keys = layer.keys;
343
344 // fast track
345 if (!keys || keys.length === 0) {
346 return done();
347 }
348
349 var i = 0;
350 var name;
351 var paramIndex = 0;
352 var key;
353 var paramVal;
354 var paramCallbacks;
355 var paramCalled;
356
357 // process params in order
358 // param callbacks can be async
359 function param(err) {
360 if (err) {
361 return done(err);
362 }
363
364 if (i >= keys.length ) {
365 return done();
366 }
367
368 paramIndex = 0;
369 key = keys[i++];
370 name = key.name;
371 paramVal = req.params[name];
372 paramCallbacks = params[name];
373 paramCalled = called[name];
374
375 if (paramVal === undefined || !paramCallbacks) {
376 return param();
377 }
378
379 // param previously called with same value or error occurred
380 if (paramCalled && (paramCalled.match === paramVal
381 || (paramCalled.error && paramCalled.error !== 'route'))) {
382 // restore value
383 req.params[name] = paramCalled.value;
384
385 // next param
386 return param(paramCalled.error);
387 }
388
389 called[name] = paramCalled = {
390 error: null,
391 match: paramVal,
392 value: paramVal
393 };
394
395 paramCallback();
396 }
397
398 // single param callbacks
399 function paramCallback(err) {
400 var fn = paramCallbacks[paramIndex++];
401
402 // store updated value
403 paramCalled.value = req.params[key.name];
404
405 if (err) {
406 // store error
407 paramCalled.error = err;
408 param(err);
409 return;
410 }
411
412 if (!fn) return param();
413
414 try {
415 fn(req, res, paramCallback, paramVal, key.name);
416 } catch (e) {
417 paramCallback(e);
418 }
419 }
420
421 param();
422};
423
424/**
425 * Use the given middleware function, with optional path, defaulting to "/".
426 *
427 * Use (like `.all`) will run for any http METHOD, but it will not add
428 * handlers for those methods so OPTIONS requests will not consider `.use`
429 * functions even if they could respond.
430 *
431 * The other difference is that _route_ path is stripped and not visible
432 * to the handler function. The main effect of this feature is that mounted
433 * handlers can operate without any code changes regardless of the "prefix"
434 * pathname.
435 *
436 * @public
437 */
438
439proto.use = function use(fn) {
440 var offset = 0;
441 var path = '/';
442
443 // default path to '/'
444 // disambiguate router.use([fn])
445 if (typeof fn !== 'function') {
446 var arg = fn;
447
448 while (Array.isArray(arg) && arg.length !== 0) {
449 arg = arg[0];
450 }
451
452 // first arg is the path
453 if (typeof arg !== 'function') {
454 offset = 1;
455 path = fn;
456 }
457 }
458
459 var callbacks = flatten(slice.call(arguments, offset));
460
461 if (callbacks.length === 0) {
462 throw new TypeError('Router.use() requires a middleware function')
463 }
464
465 for (var i = 0; i < callbacks.length; i++) {
466 var fn = callbacks[i];
467
468 if (typeof fn !== 'function') {
469 throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
470 }
471
472 // add the middleware
473 debug('use %o %s', path, fn.name || '<anonymous>')
474
475 var layer = new Layer(path, {
476 sensitive: this.caseSensitive,
477 strict: false,
478 end: false
479 }, fn);
480
481 layer.route = undefined;
482
483 this.stack.push(layer);
484 }
485
486 return this;
487};
488
489/**
490 * Create a new Route for the given path.
491 *
492 * Each route contains a separate middleware stack and VERB handlers.
493 *
494 * See the Route api documentation for details on adding handlers
495 * and middleware to routes.
496 *
497 * @param {String} path
498 * @return {Route}
499 * @public
500 */
501
502proto.route = function route(path) {
503 var route = new Route(path);
504
505 var layer = new Layer(path, {
506 sensitive: this.caseSensitive,
507 strict: this.strict,
508 end: true
509 }, route.dispatch.bind(route));
510
511 layer.route = route;
512
513 this.stack.push(layer);
514 return route;
515};
516
517// create Router#VERB functions
518methods.concat('all').forEach(function(method){
519 proto[method] = function(path){
520 var route = this.route(path)
521 route[method].apply(route, slice.call(arguments, 1));
522 return this;
523 };
524});
525
526// append methods to a list of methods
527function appendMethods(list, addition) {
528 for (var i = 0; i < addition.length; i++) {
529 var method = addition[i];
530 if (list.indexOf(method) === -1) {
531 list.push(method);
532 }
533 }
534}
535
536// get pathname of request
537function getPathname(req) {
538 try {
539 return parseUrl(req).pathname;
540 } catch (err) {
541 return undefined;
542 }
543}
544
545// Get get protocol + host for a URL
546function getProtohost(url) {
547 if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
548 return undefined
549 }
550
551 var searchIndex = url.indexOf('?')
552 var pathLength = searchIndex !== -1
553 ? searchIndex
554 : url.length
555 var fqdnIndex = url.slice(0, pathLength).indexOf('://')
556
557 return fqdnIndex !== -1
558 ? url.substring(0, url.indexOf('/', 3 + fqdnIndex))
559 : undefined
560}
561
562// get type for error message
563function gettype(obj) {
564 var type = typeof obj;
565
566 if (type !== 'object') {
567 return type;
568 }
569
570 // inspect [[Class]] for objects
571 return toString.call(obj)
572 .replace(objectRegExp, '$1');
573}
574
575/**
576 * Match path to a layer.
577 *
578 * @param {Layer} layer
579 * @param {string} path
580 * @private
581 */
582
583function matchLayer(layer, path) {
584 try {
585 return layer.match(path);
586 } catch (err) {
587 return err;
588 }
589}
590
591// merge params with parent params
592function mergeParams(params, parent) {
593 if (typeof parent !== 'object' || !parent) {
594 return params;
595 }
596
597 // make copy of parent for base
598 var obj = mixin({}, parent);
599
600 // simple non-numeric merging
601 if (!(0 in params) || !(0 in parent)) {
602 return mixin(obj, params);
603 }
604
605 var i = 0;
606 var o = 0;
607
608 // determine numeric gaps
609 while (i in params) {
610 i++;
611 }
612
613 while (o in parent) {
614 o++;
615 }
616
617 // offset numeric indices in params before merge
618 for (i--; i >= 0; i--) {
619 params[i + o] = params[i];
620
621 // create holes for the merge when necessary
622 if (i < o) {
623 delete params[i];
624 }
625 }
626
627 return mixin(obj, params);
628}
629
630// restore obj props after function
631function restore(fn, obj) {
632 var props = new Array(arguments.length - 2);
633 var vals = new Array(arguments.length - 2);
634
635 for (var i = 0; i < props.length; i++) {
636 props[i] = arguments[i + 2];
637 vals[i] = obj[props[i]];
638 }
639
640 return function () {
641 // restore vals
642 for (var i = 0; i < props.length; i++) {
643 obj[props[i]] = vals[i];
644 }
645
646 return fn.apply(this, arguments);
647 };
648}
649
650// send an OPTIONS response
651function sendOptionsResponse(res, options, next) {
652 try {
653 var body = options.join(',');
654 res.set('Allow', body);
655 res.send(body);
656 } catch (err) {
657 next(err);
658 }
659}
660
661// wrap a function
662function wrap(old, fn) {
663 return function proxy() {
664 var args = new Array(arguments.length + 1);
665
666 args[0] = old;
667 for (var i = 0, len = arguments.length; i < len; i++) {
668 args[i + 1] = arguments[i];
669 }
670
671 fn.apply(this, args);
672 };
673}
Note: See TracBrowser for help on using the repository browser.