1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | 'use strict';
|
10 |
|
11 |
|
12 |
|
13 |
|
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 |
|
28 |
|
29 |
|
30 |
|
31 | var objectRegExp = /^\[object (\S+)\]$/;
|
32 | var slice = Array.prototype.slice;
|
33 | var toString = Object.prototype.toString;
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
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 |
|
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 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | proto.param = function param(name, fn) {
|
98 |
|
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 |
|
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 |
|
122 |
|
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 |
|
133 |
|
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 |
|
148 |
|
149 | var options = [];
|
150 |
|
151 |
|
152 | var stack = self.stack;
|
153 |
|
154 |
|
155 | var parentParams = req.params;
|
156 | var parentUrl = req.baseUrl || '';
|
157 | var done = restore(out, req, 'baseUrl', 'next', 'params');
|
158 |
|
159 |
|
160 | req.next = next;
|
161 |
|
162 |
|
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 |
|
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 |
|
182 | if (slashAdded) {
|
183 | req.url = req.url.substr(1);
|
184 | slashAdded = false;
|
185 | }
|
186 |
|
187 |
|
188 | if (removed.length !== 0) {
|
189 | req.baseUrl = parentUrl;
|
190 | req.url = protohost + removed + req.url.substr(protohost.length);
|
191 | removed = '';
|
192 | }
|
193 |
|
194 |
|
195 | if (layerError === 'router') {
|
196 | setImmediate(done, null)
|
197 | return
|
198 | }
|
199 |
|
200 |
|
201 | if (idx >= stack.length) {
|
202 | setImmediate(done, layerError);
|
203 | return;
|
204 | }
|
205 |
|
206 |
|
207 | var path = getPathname(req);
|
208 |
|
209 | if (path == null) {
|
210 | return done(layerError);
|
211 | }
|
212 |
|
213 |
|
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 |
|
225 | layerError = layerError || match;
|
226 | }
|
227 |
|
228 | if (match !== true) {
|
229 | continue;
|
230 | }
|
231 |
|
232 | if (!route) {
|
233 |
|
234 | continue;
|
235 | }
|
236 |
|
237 | if (layerError) {
|
238 |
|
239 | match = false;
|
240 | continue;
|
241 | }
|
242 |
|
243 | var method = req.method;
|
244 | var has_method = route._handles_method(method);
|
245 |
|
246 |
|
247 | if (!has_method && method === 'OPTIONS') {
|
248 | appendMethods(options, route._options());
|
249 | }
|
250 |
|
251 |
|
252 | if (!has_method && method !== 'HEAD') {
|
253 | match = false;
|
254 | continue;
|
255 | }
|
256 | }
|
257 |
|
258 |
|
259 | if (match !== true) {
|
260 | return done(layerError);
|
261 | }
|
262 |
|
263 |
|
264 | if (route) {
|
265 | req.route = route;
|
266 | }
|
267 |
|
268 |
|
269 | req.params = self.mergeParams
|
270 | ? mergeParams(layer.params, parentParams)
|
271 | : layer.params;
|
272 | var layerPath = layer.path;
|
273 |
|
274 |
|
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 |
|
291 | var c = path[layerPath.length]
|
292 | if (c && c !== '/' && c !== '.') return next(layerError)
|
293 |
|
294 |
|
295 |
|
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 |
|
301 | if (!protohost && req.url[0] !== '/') {
|
302 | req.url = '/' + req.url;
|
303 | slashAdded = true;
|
304 | }
|
305 |
|
306 |
|
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 |
|
324 |
|
325 |
|
326 |
|
327 | proto.process_params = function process_params(layer, called, req, res, done) {
|
328 | var params = this.params;
|
329 |
|
330 |
|
331 | var keys = layer.keys;
|
332 |
|
333 |
|
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 |
|
347 |
|
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 |
|
369 | if (paramCalled && (paramCalled.match === paramVal
|
370 | || (paramCalled.error && paramCalled.error !== 'route'))) {
|
371 |
|
372 | req.params[name] = paramCalled.value;
|
373 |
|
374 |
|
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 |
|
388 | function paramCallback(err) {
|
389 | var fn = paramCallbacks[paramIndex++];
|
390 |
|
391 |
|
392 | paramCalled.value = req.params[key.name];
|
393 |
|
394 | if (err) {
|
395 |
|
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 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 | proto.use = function use(fn) {
|
429 | var offset = 0;
|
430 | var path = '/';
|
431 |
|
432 |
|
433 |
|
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 |
|
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 |
|
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 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
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 |
|
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 |
|
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 |
|
526 | function getPathname(req) {
|
527 | try {
|
528 | return parseUrl(req).pathname;
|
529 | } catch (err) {
|
530 | return undefined;
|
531 | }
|
532 | }
|
533 |
|
534 |
|
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 |
|
552 | function gettype(obj) {
|
553 | var type = typeof obj;
|
554 |
|
555 | if (type !== 'object') {
|
556 | return type;
|
557 | }
|
558 |
|
559 |
|
560 | return toString.call(obj)
|
561 | .replace(objectRegExp, '$1');
|
562 | }
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 | function matchLayer(layer, path) {
|
573 | try {
|
574 | return layer.match(path);
|
575 | } catch (err) {
|
576 | return err;
|
577 | }
|
578 | }
|
579 |
|
580 |
|
581 | function mergeParams(params, parent) {
|
582 | if (typeof parent !== 'object' || !parent) {
|
583 | return params;
|
584 | }
|
585 |
|
586 |
|
587 | var obj = mixin({}, parent);
|
588 |
|
589 |
|
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 |
|
598 | while (i in params) {
|
599 | i++;
|
600 | }
|
601 |
|
602 | while (o in parent) {
|
603 | o++;
|
604 | }
|
605 |
|
606 |
|
607 | for (i--; i >= 0; i--) {
|
608 | params[i + o] = params[i];
|
609 |
|
610 |
|
611 | if (i < o) {
|
612 | delete params[i];
|
613 | }
|
614 | }
|
615 |
|
616 | return mixin(obj, params);
|
617 | }
|
618 |
|
619 |
|
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 |
|
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 |
|
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 |
|
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 | }
|