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