1 | /** |
---|
2 | * Module dependencies. |
---|
3 | */ |
---|
4 | |
---|
5 | var connect = require('connect') |
---|
6 | , Router = require('./router') |
---|
7 | , methods = require('methods') |
---|
8 | , middleware = require('./middleware') |
---|
9 | , debug = require('debug')('express:application') |
---|
10 | , locals = require('./utils').locals |
---|
11 | , View = require('./view') |
---|
12 | , utils = connect.utils |
---|
13 | , path = require('path') |
---|
14 | , http = require('http') |
---|
15 | , join = path.join; |
---|
16 | |
---|
17 | /** |
---|
18 | * Application prototype. |
---|
19 | */ |
---|
20 | |
---|
21 | var app = exports = module.exports = {}; |
---|
22 | |
---|
23 | /** |
---|
24 | * Initialize the server. |
---|
25 | * |
---|
26 | * - setup default configuration |
---|
27 | * - setup default middleware |
---|
28 | * - setup route reflection methods |
---|
29 | * |
---|
30 | * @api private |
---|
31 | */ |
---|
32 | |
---|
33 | app.init = function(){ |
---|
34 | this.cache = {}; |
---|
35 | this.settings = {}; |
---|
36 | this.engines = {}; |
---|
37 | this.defaultConfiguration(); |
---|
38 | }; |
---|
39 | |
---|
40 | /** |
---|
41 | * Initialize application configuration. |
---|
42 | * |
---|
43 | * @api private |
---|
44 | */ |
---|
45 | |
---|
46 | app.defaultConfiguration = function(){ |
---|
47 | // default settings |
---|
48 | this.enable('x-powered-by'); |
---|
49 | this.set('env', process.env.NODE_ENV || 'development'); |
---|
50 | this.set('subdomain offset', 2); |
---|
51 | debug('booting in %s mode', this.get('env')); |
---|
52 | |
---|
53 | // implicit middleware |
---|
54 | this.use(connect.query()); |
---|
55 | this.use(middleware.init(this)); |
---|
56 | |
---|
57 | // inherit protos |
---|
58 | this.on('mount', function(parent){ |
---|
59 | this.request.__proto__ = parent.request; |
---|
60 | this.response.__proto__ = parent.response; |
---|
61 | this.engines.__proto__ = parent.engines; |
---|
62 | this.settings.__proto__ = parent.settings; |
---|
63 | }); |
---|
64 | |
---|
65 | // router |
---|
66 | this._router = new Router(this); |
---|
67 | this.routes = this._router.map; |
---|
68 | this.__defineGetter__('router', function(){ |
---|
69 | this._usedRouter = true; |
---|
70 | this._router.caseSensitive = this.enabled('case sensitive routing'); |
---|
71 | this._router.strict = this.enabled('strict routing'); |
---|
72 | return this._router.middleware; |
---|
73 | }); |
---|
74 | |
---|
75 | // setup locals |
---|
76 | this.locals = locals(this); |
---|
77 | |
---|
78 | // default locals |
---|
79 | this.locals.settings = this.settings; |
---|
80 | |
---|
81 | // default configuration |
---|
82 | this.set('view', View); |
---|
83 | this.set('views', process.cwd() + '/views'); |
---|
84 | this.set('jsonp callback name', 'callback'); |
---|
85 | |
---|
86 | this.configure('development', function(){ |
---|
87 | this.set('json spaces', 2); |
---|
88 | }); |
---|
89 | |
---|
90 | this.configure('production', function(){ |
---|
91 | this.enable('view cache'); |
---|
92 | }); |
---|
93 | }; |
---|
94 | |
---|
95 | /** |
---|
96 | * Proxy `connect#use()` to apply settings to |
---|
97 | * mounted applications. |
---|
98 | * |
---|
99 | * @param {String|Function|Server} route |
---|
100 | * @param {Function|Server} fn |
---|
101 | * @return {app} for chaining |
---|
102 | * @api public |
---|
103 | */ |
---|
104 | |
---|
105 | app.use = function(route, fn){ |
---|
106 | var app; |
---|
107 | |
---|
108 | // default route to '/' |
---|
109 | if ('string' != typeof route) fn = route, route = '/'; |
---|
110 | |
---|
111 | // express app |
---|
112 | if (fn.handle && fn.set) app = fn; |
---|
113 | |
---|
114 | // restore .app property on req and res |
---|
115 | if (app) { |
---|
116 | app.route = route; |
---|
117 | fn = function(req, res, next) { |
---|
118 | var orig = req.app; |
---|
119 | app.handle(req, res, function(err){ |
---|
120 | req.app = res.app = orig; |
---|
121 | req.__proto__ = orig.request; |
---|
122 | res.__proto__ = orig.response; |
---|
123 | next(err); |
---|
124 | }); |
---|
125 | }; |
---|
126 | } |
---|
127 | |
---|
128 | connect.proto.use.call(this, route, fn); |
---|
129 | |
---|
130 | // mounted an app |
---|
131 | if (app) { |
---|
132 | app.parent = this; |
---|
133 | app.emit('mount', this); |
---|
134 | } |
---|
135 | |
---|
136 | return this; |
---|
137 | }; |
---|
138 | |
---|
139 | /** |
---|
140 | * Register the given template engine callback `fn` |
---|
141 | * as `ext`. |
---|
142 | * |
---|
143 | * By default will `require()` the engine based on the |
---|
144 | * file extension. For example if you try to render |
---|
145 | * a "foo.jade" file Express will invoke the following internally: |
---|
146 | * |
---|
147 | * app.engine('jade', require('jade').__express); |
---|
148 | * |
---|
149 | * For engines that do not provide `.__express` out of the box, |
---|
150 | * or if you wish to "map" a different extension to the template engine |
---|
151 | * you may use this method. For example mapping the EJS template engine to |
---|
152 | * ".html" files: |
---|
153 | * |
---|
154 | * app.engine('html', require('ejs').renderFile); |
---|
155 | * |
---|
156 | * In this case EJS provides a `.renderFile()` method with |
---|
157 | * the same signature that Express expects: `(path, options, callback)`, |
---|
158 | * though note that it aliases this method as `ejs.__express` internally |
---|
159 | * so if you're using ".ejs" extensions you dont need to do anything. |
---|
160 | * |
---|
161 | * Some template engines do not follow this convention, the |
---|
162 | * [Consolidate.js](https://github.com/visionmedia/consolidate.js) |
---|
163 | * library was created to map all of node's popular template |
---|
164 | * engines to follow this convention, thus allowing them to |
---|
165 | * work seamlessly within Express. |
---|
166 | * |
---|
167 | * @param {String} ext |
---|
168 | * @param {Function} fn |
---|
169 | * @return {app} for chaining |
---|
170 | * @api public |
---|
171 | */ |
---|
172 | |
---|
173 | app.engine = function(ext, fn){ |
---|
174 | if ('function' != typeof fn) throw new Error('callback function required'); |
---|
175 | if ('.' != ext[0]) ext = '.' + ext; |
---|
176 | this.engines[ext] = fn; |
---|
177 | return this; |
---|
178 | }; |
---|
179 | |
---|
180 | /** |
---|
181 | * Map the given param placeholder `name`(s) to the given callback(s). |
---|
182 | * |
---|
183 | * Parameter mapping is used to provide pre-conditions to routes |
---|
184 | * which use normalized placeholders. For example a _:user_id_ parameter |
---|
185 | * could automatically load a user's information from the database without |
---|
186 | * any additional code, |
---|
187 | * |
---|
188 | * The callback uses the samesignature as middleware, the only differencing |
---|
189 | * being that the value of the placeholder is passed, in this case the _id_ |
---|
190 | * of the user. Once the `next()` function is invoked, just like middleware |
---|
191 | * it will continue on to execute the route, or subsequent parameter functions. |
---|
192 | * |
---|
193 | * app.param('user_id', function(req, res, next, id){ |
---|
194 | * User.find(id, function(err, user){ |
---|
195 | * if (err) { |
---|
196 | * next(err); |
---|
197 | * } else if (user) { |
---|
198 | * req.user = user; |
---|
199 | * next(); |
---|
200 | * } else { |
---|
201 | * next(new Error('failed to load user')); |
---|
202 | * } |
---|
203 | * }); |
---|
204 | * }); |
---|
205 | * |
---|
206 | * @param {String|Array} name |
---|
207 | * @param {Function} fn |
---|
208 | * @return {app} for chaining |
---|
209 | * @api public |
---|
210 | */ |
---|
211 | |
---|
212 | app.param = function(name, fn){ |
---|
213 | var self = this |
---|
214 | , fns = [].slice.call(arguments, 1); |
---|
215 | |
---|
216 | // array |
---|
217 | if (Array.isArray(name)) { |
---|
218 | name.forEach(function(name){ |
---|
219 | fns.forEach(function(fn){ |
---|
220 | self.param(name, fn); |
---|
221 | }); |
---|
222 | }); |
---|
223 | // param logic |
---|
224 | } else if ('function' == typeof name) { |
---|
225 | this._router.param(name); |
---|
226 | // single |
---|
227 | } else { |
---|
228 | if (':' == name[0]) name = name.substr(1); |
---|
229 | fns.forEach(function(fn){ |
---|
230 | self._router.param(name, fn); |
---|
231 | }); |
---|
232 | } |
---|
233 | |
---|
234 | return this; |
---|
235 | }; |
---|
236 | |
---|
237 | /** |
---|
238 | * Assign `setting` to `val`, or return `setting`'s value. |
---|
239 | * |
---|
240 | * app.set('foo', 'bar'); |
---|
241 | * app.get('foo'); |
---|
242 | * // => "bar" |
---|
243 | * |
---|
244 | * Mounted servers inherit their parent server's settings. |
---|
245 | * |
---|
246 | * @param {String} setting |
---|
247 | * @param {String} val |
---|
248 | * @return {Server} for chaining |
---|
249 | * @api public |
---|
250 | */ |
---|
251 | |
---|
252 | app.set = function(setting, val){ |
---|
253 | if (1 == arguments.length) { |
---|
254 | return this.settings[setting]; |
---|
255 | } else { |
---|
256 | this.settings[setting] = val; |
---|
257 | return this; |
---|
258 | } |
---|
259 | }; |
---|
260 | |
---|
261 | /** |
---|
262 | * Return the app's absolute pathname |
---|
263 | * based on the parent(s) that have |
---|
264 | * mounted it. |
---|
265 | * |
---|
266 | * For example if the application was |
---|
267 | * mounted as "/admin", which itself |
---|
268 | * was mounted as "/blog" then the |
---|
269 | * return value would be "/blog/admin". |
---|
270 | * |
---|
271 | * @return {String} |
---|
272 | * @api private |
---|
273 | */ |
---|
274 | |
---|
275 | app.path = function(){ |
---|
276 | return this.parent |
---|
277 | ? this.parent.path() + this.route |
---|
278 | : ''; |
---|
279 | }; |
---|
280 | |
---|
281 | /** |
---|
282 | * Check if `setting` is enabled (truthy). |
---|
283 | * |
---|
284 | * app.enabled('foo') |
---|
285 | * // => false |
---|
286 | * |
---|
287 | * app.enable('foo') |
---|
288 | * app.enabled('foo') |
---|
289 | * // => true |
---|
290 | * |
---|
291 | * @param {String} setting |
---|
292 | * @return {Boolean} |
---|
293 | * @api public |
---|
294 | */ |
---|
295 | |
---|
296 | app.enabled = function(setting){ |
---|
297 | return !!this.set(setting); |
---|
298 | }; |
---|
299 | |
---|
300 | /** |
---|
301 | * Check if `setting` is disabled. |
---|
302 | * |
---|
303 | * app.disabled('foo') |
---|
304 | * // => true |
---|
305 | * |
---|
306 | * app.enable('foo') |
---|
307 | * app.disabled('foo') |
---|
308 | * // => false |
---|
309 | * |
---|
310 | * @param {String} setting |
---|
311 | * @return {Boolean} |
---|
312 | * @api public |
---|
313 | */ |
---|
314 | |
---|
315 | app.disabled = function(setting){ |
---|
316 | return !this.set(setting); |
---|
317 | }; |
---|
318 | |
---|
319 | /** |
---|
320 | * Enable `setting`. |
---|
321 | * |
---|
322 | * @param {String} setting |
---|
323 | * @return {app} for chaining |
---|
324 | * @api public |
---|
325 | */ |
---|
326 | |
---|
327 | app.enable = function(setting){ |
---|
328 | return this.set(setting, true); |
---|
329 | }; |
---|
330 | |
---|
331 | /** |
---|
332 | * Disable `setting`. |
---|
333 | * |
---|
334 | * @param {String} setting |
---|
335 | * @return {app} for chaining |
---|
336 | * @api public |
---|
337 | */ |
---|
338 | |
---|
339 | app.disable = function(setting){ |
---|
340 | return this.set(setting, false); |
---|
341 | }; |
---|
342 | |
---|
343 | /** |
---|
344 | * Configure callback for zero or more envs, |
---|
345 | * when no `env` is specified that callback will |
---|
346 | * be invoked for all environments. Any combination |
---|
347 | * can be used multiple times, in any order desired. |
---|
348 | * |
---|
349 | * Examples: |
---|
350 | * |
---|
351 | * app.configure(function(){ |
---|
352 | * // executed for all envs |
---|
353 | * }); |
---|
354 | * |
---|
355 | * app.configure('stage', function(){ |
---|
356 | * // executed staging env |
---|
357 | * }); |
---|
358 | * |
---|
359 | * app.configure('stage', 'production', function(){ |
---|
360 | * // executed for stage and production |
---|
361 | * }); |
---|
362 | * |
---|
363 | * Note: |
---|
364 | * |
---|
365 | * These callbacks are invoked immediately, and |
---|
366 | * are effectively sugar for the following: |
---|
367 | * |
---|
368 | * var env = process.env.NODE_ENV || 'development'; |
---|
369 | * |
---|
370 | * switch (env) { |
---|
371 | * case 'development': |
---|
372 | * ... |
---|
373 | * break; |
---|
374 | * case 'stage': |
---|
375 | * ... |
---|
376 | * break; |
---|
377 | * case 'production': |
---|
378 | * ... |
---|
379 | * break; |
---|
380 | * } |
---|
381 | * |
---|
382 | * @param {String} env... |
---|
383 | * @param {Function} fn |
---|
384 | * @return {app} for chaining |
---|
385 | * @api public |
---|
386 | */ |
---|
387 | |
---|
388 | app.configure = function(env, fn){ |
---|
389 | var envs = 'all' |
---|
390 | , args = [].slice.call(arguments); |
---|
391 | fn = args.pop(); |
---|
392 | if (args.length) envs = args; |
---|
393 | if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this); |
---|
394 | return this; |
---|
395 | }; |
---|
396 | |
---|
397 | /** |
---|
398 | * Delegate `.VERB(...)` calls to `router.VERB(...)`. |
---|
399 | */ |
---|
400 | |
---|
401 | methods.forEach(function(method){ |
---|
402 | app[method] = function(path){ |
---|
403 | if ('get' == method && 1 == arguments.length) return this.set(path); |
---|
404 | |
---|
405 | // deprecated |
---|
406 | if (Array.isArray(path)) { |
---|
407 | console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0'); |
---|
408 | } |
---|
409 | |
---|
410 | // if no router attached yet, attach the router |
---|
411 | if (!this._usedRouter) this.use(this.router); |
---|
412 | |
---|
413 | // setup route |
---|
414 | this._router[method].apply(this._router, arguments); |
---|
415 | return this; |
---|
416 | }; |
---|
417 | }); |
---|
418 | |
---|
419 | /** |
---|
420 | * Special-cased "all" method, applying the given route `path`, |
---|
421 | * middleware, and callback to _every_ HTTP method. |
---|
422 | * |
---|
423 | * @param {String} path |
---|
424 | * @param {Function} ... |
---|
425 | * @return {app} for chaining |
---|
426 | * @api public |
---|
427 | */ |
---|
428 | |
---|
429 | app.all = function(path){ |
---|
430 | var args = arguments; |
---|
431 | methods.forEach(function(method){ |
---|
432 | app[method].apply(this, args); |
---|
433 | }, this); |
---|
434 | return this; |
---|
435 | }; |
---|
436 | |
---|
437 | // del -> delete alias |
---|
438 | |
---|
439 | app.del = app.delete; |
---|
440 | |
---|
441 | /** |
---|
442 | * Render the given view `name` name with `options` |
---|
443 | * and a callback accepting an error and the |
---|
444 | * rendered template string. |
---|
445 | * |
---|
446 | * Example: |
---|
447 | * |
---|
448 | * app.render('email', { name: 'Tobi' }, function(err, html){ |
---|
449 | * // ... |
---|
450 | * }) |
---|
451 | * |
---|
452 | * @param {String} name |
---|
453 | * @param {String|Function} options or fn |
---|
454 | * @param {Function} fn |
---|
455 | * @api public |
---|
456 | */ |
---|
457 | |
---|
458 | app.render = function(name, options, fn){ |
---|
459 | var opts = {} |
---|
460 | , cache = this.cache |
---|
461 | , engines = this.engines |
---|
462 | , view; |
---|
463 | |
---|
464 | // support callback function as second arg |
---|
465 | if ('function' == typeof options) { |
---|
466 | fn = options, options = {}; |
---|
467 | } |
---|
468 | |
---|
469 | // merge app.locals |
---|
470 | utils.merge(opts, this.locals); |
---|
471 | |
---|
472 | // merge options._locals |
---|
473 | if (options._locals) utils.merge(opts, options._locals); |
---|
474 | |
---|
475 | // merge options |
---|
476 | utils.merge(opts, options); |
---|
477 | |
---|
478 | // set .cache unless explicitly provided |
---|
479 | opts.cache = null == opts.cache |
---|
480 | ? this.enabled('view cache') |
---|
481 | : opts.cache; |
---|
482 | |
---|
483 | // primed cache |
---|
484 | if (opts.cache) view = cache[name]; |
---|
485 | |
---|
486 | // view |
---|
487 | if (!view) { |
---|
488 | view = new (this.get('view'))(name, { |
---|
489 | defaultEngine: this.get('view engine'), |
---|
490 | root: this.get('views'), |
---|
491 | engines: engines |
---|
492 | }); |
---|
493 | |
---|
494 | if (!view.path) { |
---|
495 | var err = new Error('Failed to lookup view "' + name + '"'); |
---|
496 | err.view = view; |
---|
497 | return fn(err); |
---|
498 | } |
---|
499 | |
---|
500 | // prime the cache |
---|
501 | if (opts.cache) cache[name] = view; |
---|
502 | } |
---|
503 | |
---|
504 | // render |
---|
505 | try { |
---|
506 | view.render(opts, fn); |
---|
507 | } catch (err) { |
---|
508 | fn(err); |
---|
509 | } |
---|
510 | }; |
---|
511 | |
---|
512 | /** |
---|
513 | * Listen for connections. |
---|
514 | * |
---|
515 | * A node `http.Server` is returned, with this |
---|
516 | * application (which is a `Function`) as its |
---|
517 | * callback. If you wish to create both an HTTP |
---|
518 | * and HTTPS server you may do so with the "http" |
---|
519 | * and "https" modules as shown here: |
---|
520 | * |
---|
521 | * var http = require('http') |
---|
522 | * , https = require('https') |
---|
523 | * , express = require('express') |
---|
524 | * , app = express(); |
---|
525 | * |
---|
526 | * http.createServer(app).listen(80); |
---|
527 | * https.createServer({ ... }, app).listen(443); |
---|
528 | * |
---|
529 | * @return {http.Server} |
---|
530 | * @api public |
---|
531 | */ |
---|
532 | |
---|
533 | app.listen = function(){ |
---|
534 | var server = http.createServer(this); |
---|
535 | return server.listen.apply(server, arguments); |
---|
536 | }; |
---|