Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50654 views
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
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
* Module variables.
28
* @private
29
*/
30
31
var objectRegExp = /^\[object (\S+)\]$/;
32
var slice = Array.prototype.slice;
33
var 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
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
// 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
97
proto.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
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
// 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
327
proto.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
428
proto.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 middleware functions');
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 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
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
// create Router#VERB functions
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
// append methods to a list of methods
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
// get pathname of request
526
function 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
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
// get type for error message
552
function 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
572
function 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
581
function 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
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
// 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
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
// wrap a function
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
}
663
664