Mercurial > hg > nodescore
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 }); |