UNPKG

12.9 kBJavaScriptView Raw
1/**
2 * Module dependencies.
3 */
4
5var mixin = require('utils-merge');
6var escapeHtml = require('escape-html');
7var Router = require('./router');
8var methods = require('methods');
9var middleware = require('./middleware/init');
10var query = require('./middleware/query');
11var debug = require('debug')('express:application');
12var View = require('./view');
13var http = require('http');
14var compileTrust = require('./utils').compileTrust;
15var deprecate = require('./utils').deprecate;
16
17/**
18 * Application prototype.
19 */
20
21var 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
33app.init = function(){
34 this._baseRoutes = {};
35 this.cache = {};
36 this.settings = {};
37 this.engines = {};
38 this.defaultConfiguration();
39};
40
41/**
42 * Initialize application configuration.
43 *
44 * @api private
45 */
46
47app.defaultConfiguration = function(){
48 // default settings
49 this.enable('x-powered-by');
50 this.enable('etag');
51 var env = process.env.NODE_ENV || 'development';
52 this.set('env', env);
53 this.set('subdomain offset', 2);
54 this.set('trust proxy', false);
55
56 debug('booting in %s mode', env);
57
58 // inherit protos
59 this.on('mount', function(parent){
60 this.request.__proto__ = parent.request;
61 this.response.__proto__ = parent.response;
62 this.engines.__proto__ = parent.engines;
63 this.settings.__proto__ = parent.settings;
64 });
65
66 // setup locals
67 this.locals = Object.create(null);
68
69 // top-most app is mounted at /
70 this.mountpath = '/';
71
72 // default locals
73 this.locals.settings = this.settings;
74
75 // default configuration
76 this.set('view', View);
77 this.set('views', process.cwd() + '/views');
78 this.set('jsonp callback name', 'callback');
79
80 if (env === 'production') {
81 this.enable('view cache');
82 }
83
84 Object.defineProperty(this, 'router', {
85 get: function() {
86 throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
87 }
88 });
89};
90
91/**
92 * lazily adds the base router if it has not yet been added.
93 *
94 * We cannot add the base router in the defaultConfiguration because
95 * it reads app settings which might be set after that has run.
96 *
97 * @api private
98 */
99app.lazyrouter = function() {
100 if (!this._router) {
101 this._router = new Router({
102 caseSensitive: this.enabled('case sensitive routing'),
103 strict: this.enabled('strict routing')
104 });
105
106 this._router.use(query());
107 this._router.use(middleware.init(this));
108 }
109};
110
111/**
112 * Dispatch a req, res pair into the application. Starts pipeline processing.
113 *
114 * If no _done_ callback is provided, then default error handlers will respond
115 * in the event of an error bubbling through the stack.
116 *
117 * @api private
118 */
119
120app.handle = function(req, res, done) {
121 var env = this.get('env');
122
123 this._router.handle(req, res, function(err) {
124 if (done) {
125 return done(err);
126 }
127
128 // unhandled error
129 if (err) {
130 // default to 500
131 if (res.statusCode < 400) res.statusCode = 500;
132 debug('default %s', res.statusCode);
133
134 // respect err.status
135 if (err.status) res.statusCode = err.status;
136
137 // production gets a basic error message
138 var msg = 'production' == env
139 ? http.STATUS_CODES[res.statusCode]
140 : err.stack || err.toString();
141 msg = escapeHtml(msg);
142
143 // log to stderr in a non-test env
144 if ('test' != env) console.error(err.stack || err.toString());
145 if (res.headersSent) return req.socket.destroy();
146 res.setHeader('Content-Type', 'text/html');
147 res.setHeader('Content-Length', Buffer.byteLength(msg));
148 if ('HEAD' == req.method) return res.end();
149 res.end(msg);
150 return;
151 }
152
153 // 404
154 debug('default 404');
155 res.statusCode = 404;
156 res.setHeader('Content-Type', 'text/html');
157 if ('HEAD' == req.method) return res.end();
158 res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
159 });
160};
161
162/**
163 * Proxy `Router#use()` to add middleware to the app router.
164 * See Router#use() documentation for details.
165 *
166 * If the _fn_ parameter is an express app, then it will be
167 * mounted at the _route_ specified.
168 *
169 * @param {String|Function|Server} route
170 * @param {Function|Server} fn
171 * @return {app} for chaining
172 * @api public
173 */
174
175app.use = function(route, fn){
176 var mount_app;
177
178 // default route to '/'
179 if ('string' != typeof route) fn = route, route = '/';
180
181 // express app
182 if (fn.handle && fn.set) mount_app = fn;
183
184 // restore .app property on req and res
185 if (mount_app) {
186 debug('.use app under %s', route);
187 mount_app.mountpath = route;
188 fn = function(req, res, next) {
189 var orig = req.app;
190 mount_app.handle(req, res, function(err) {
191 req.__proto__ = orig.request;
192 res.__proto__ = orig.response;
193 next(err);
194 });
195 };
196 }
197
198 this.lazyrouter();
199 this._router.use(route, fn);
200
201 // mounted an app
202 if (mount_app) {
203 mount_app.parent = this;
204 mount_app.emit('mount', this);
205 }
206
207 return this;
208};
209
210/**
211 * Proxy to the app `Router#route()`
212 * Returns a new `Route` instance for the _path_.
213 *
214 * Routes are isolated middleware stacks for specific paths.
215 * See the Route api docs for details.
216 *
217 * @api public
218 */
219
220app.route = function(path){
221 this.lazyrouter();
222 return this._router.route(path);
223};
224
225/**
226 * Register the given template engine callback `fn`
227 * as `ext`.
228 *
229 * By default will `require()` the engine based on the
230 * file extension. For example if you try to render
231 * a "foo.jade" file Express will invoke the following internally:
232 *
233 * app.engine('jade', require('jade').__express);
234 *
235 * For engines that do not provide `.__express` out of the box,
236 * or if you wish to "map" a different extension to the template engine
237 * you may use this method. For example mapping the EJS template engine to
238 * ".html" files:
239 *
240 * app.engine('html', require('ejs').renderFile);
241 *
242 * In this case EJS provides a `.renderFile()` method with
243 * the same signature that Express expects: `(path, options, callback)`,
244 * though note that it aliases this method as `ejs.__express` internally
245 * so if you're using ".ejs" extensions you dont need to do anything.
246 *
247 * Some template engines do not follow this convention, the
248 * [Consolidate.js](https://github.com/visionmedia/consolidate.js)
249 * library was created to map all of node's popular template
250 * engines to follow this convention, thus allowing them to
251 * work seamlessly within Express.
252 *
253 * @param {String} ext
254 * @param {Function} fn
255 * @return {app} for chaining
256 * @api public
257 */
258
259app.engine = function(ext, fn){
260 if ('function' != typeof fn) throw new Error('callback function required');
261 if ('.' != ext[0]) ext = '.' + ext;
262 this.engines[ext] = fn;
263 return this;
264};
265
266/**
267 * Proxy to `Router#param()` with one added api feature. The _name_ parameter
268 * can be an array of names.
269 *
270 * See the Router#param() docs for more details.
271 *
272 * @param {String|Array} name
273 * @param {Function} fn
274 * @return {app} for chaining
275 * @api public
276 */
277
278app.param = function(name, fn){
279 var self = this;
280 self.lazyrouter();
281
282 if (Array.isArray(name)) {
283 name.forEach(function(key) {
284 self.param(key, fn);
285 });
286 return this;
287 }
288
289 self._router.param(name, fn);
290 return this;
291};
292
293/**
294 * Assign `setting` to `val`, or return `setting`'s value.
295 *
296 * app.set('foo', 'bar');
297 * app.get('foo');
298 * // => "bar"
299 *
300 * Mounted servers inherit their parent server's settings.
301 *
302 * @param {String} setting
303 * @param {*} [val]
304 * @return {Server} for chaining
305 * @api public
306 */
307
308app.set = function(setting, val){
309 if (1 == arguments.length) {
310 return this.settings[setting];
311 } else {
312 this.settings[setting] = val;
313
314 if (setting === 'trust proxy') {
315 debug('compile trust proxy %j', val);
316 this.set('trust proxy fn', compileTrust(val));
317 }
318
319 return this;
320 }
321};
322
323/**
324 * Return the app's absolute pathname
325 * based on the parent(s) that have
326 * mounted it.
327 *
328 * For example if the application was
329 * mounted as "/admin", which itself
330 * was mounted as "/blog" then the
331 * return value would be "/blog/admin".
332 *
333 * @return {String}
334 * @api private
335 */
336
337app.path = function(){
338 return this.parent
339 ? this.parent.path() + this.mountpath
340 : '';
341};
342
343/**
344 * Check if `setting` is enabled (truthy).
345 *
346 * app.enabled('foo')
347 * // => false
348 *
349 * app.enable('foo')
350 * app.enabled('foo')
351 * // => true
352 *
353 * @param {String} setting
354 * @return {Boolean}
355 * @api public
356 */
357
358app.enabled = function(setting){
359 return !!this.set(setting);
360};
361
362/**
363 * Check if `setting` is disabled.
364 *
365 * app.disabled('foo')
366 * // => true
367 *
368 * app.enable('foo')
369 * app.disabled('foo')
370 * // => false
371 *
372 * @param {String} setting
373 * @return {Boolean}
374 * @api public
375 */
376
377app.disabled = function(setting){
378 return !this.set(setting);
379};
380
381/**
382 * Enable `setting`.
383 *
384 * @param {String} setting
385 * @return {app} for chaining
386 * @api public
387 */
388
389app.enable = function(setting){
390 return this.set(setting, true);
391};
392
393/**
394 * Disable `setting`.
395 *
396 * @param {String} setting
397 * @return {app} for chaining
398 * @api public
399 */
400
401app.disable = function(setting){
402 return this.set(setting, false);
403};
404
405/**
406 * Delegate `.VERB(...)` calls to `router.VERB(...)`.
407 */
408
409methods.forEach(function(method){
410 app[method] = function(path){
411 if (method === 'get' && arguments.length === 1) {
412 return this.set(path);
413 }
414
415 var route = this._baseRoute(path);
416 route[method].apply(route, [].slice.call(arguments, 1));
417 return this;
418 };
419});
420
421/**
422 * Special-cased "all" method, applying the given route `path`,
423 * middleware, and callback to _every_ HTTP method.
424 *
425 * @param {String} path
426 * @param {Function} ...
427 * @return {app} for chaining
428 * @api public
429 */
430
431app.all = function(path){
432 var route = this._baseRoute(path);
433 var args = [].slice.call(arguments, 1);
434 methods.forEach(function(method){
435 route[method].apply(route, args);
436 });
437
438 return this;
439};
440
441// del -> delete alias
442
443app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
444
445/**
446 * Render the given view `name` name with `options`
447 * and a callback accepting an error and the
448 * rendered template string.
449 *
450 * Example:
451 *
452 * app.render('email', { name: 'Tobi' }, function(err, html){
453 * // ...
454 * })
455 *
456 * @param {String} name
457 * @param {String|Function} options or fn
458 * @param {Function} fn
459 * @api public
460 */
461
462app.render = function(name, options, fn){
463 var opts = {};
464 var cache = this.cache;
465 var engines = this.engines;
466 var view;
467
468 // support callback function as second arg
469 if ('function' == typeof options) {
470 fn = options, options = {};
471 }
472
473 // merge app.locals
474 mixin(opts, this.locals);
475
476 // merge options._locals
477 if (options._locals) mixin(opts, options._locals);
478
479 // merge options
480 mixin(opts, options);
481
482 // set .cache unless explicitly provided
483 opts.cache = null == opts.cache
484 ? this.enabled('view cache')
485 : opts.cache;
486
487 // primed cache
488 if (opts.cache) view = cache[name];
489
490 // view
491 if (!view) {
492 view = new (this.get('view'))(name, {
493 defaultEngine: this.get('view engine'),
494 root: this.get('views'),
495 engines: engines
496 });
497
498 if (!view.path) {
499 var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
500 err.view = view;
501 return fn(err);
502 }
503
504 // prime the cache
505 if (opts.cache) cache[name] = view;
506 }
507
508 // render
509 try {
510 view.render(opts, fn);
511 } catch (err) {
512 fn(err);
513 }
514};
515
516/**
517 * Listen for connections.
518 *
519 * A node `http.Server` is returned, with this
520 * application (which is a `Function`) as its
521 * callback. If you wish to create both an HTTP
522 * and HTTPS server you may do so with the "http"
523 * and "https" modules as shown here:
524 *
525 * var http = require('http')
526 * , https = require('https')
527 * , express = require('express')
528 * , app = express();
529 *
530 * http.createServer(app).listen(80);
531 * https.createServer({ ... }, app).listen(443);
532 *
533 * @return {http.Server}
534 * @api public
535 */
536
537app.listen = function(){
538 var server = http.createServer(this);
539 return server.listen.apply(server, arguments);
540};
541
542/**
543 * Get or create a new base route for path.
544 *
545 * @param {String} path
546 * @return {Route}
547 * @api private
548 */
549
550app._baseRoute = function(path){
551 this.lazyrouter();
552
553 var key = typeof path !== 'string'
554 ? 'o:' + String(path)
555 : 's:' + path;
556
557 if (path.ignoreCase || !this._router.caseSensitive) {
558 key = 'i' + key.toLowerCase();
559 }
560
561 var route = this._baseRoutes[key];
562
563 if (!route) {
564 route = this._router.route(path);
565 this._baseRoutes[key] = route;
566 }
567
568 return route;
569};