comparison node_modules/express/lib/router/index.js @ 73:0c3a2942ddee

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