UNPKG

14.9 kBJavaScriptView Raw
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.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
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 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
327proto.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
428proto.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
491proto.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
507methods.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
516function 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
526function 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
535function 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
552function 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
572function 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
581function 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
620function 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
640function 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
651function 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}