Allow options without filename in res.download
[express.git] / lib / application.js
blobebb30b51b3d6e84d33288251d11ab92d8462c181
1 /*!
2  * express
3  * Copyright(c) 2009-2013 TJ Holowaychuk
4  * Copyright(c) 2013 Roman Shtylman
5  * Copyright(c) 2014-2015 Douglas Christopher Wilson
6  * MIT Licensed
7  */
9 'use strict';
11 /**
12  * Module dependencies.
13  * @private
14  */
16 var finalhandler = require('finalhandler');
17 var Router = require('./router');
18 var methods = require('methods');
19 var middleware = require('./middleware/init');
20 var query = require('./middleware/query');
21 var debug = require('debug')('express:application');
22 var View = require('./view');
23 var http = require('http');
24 var compileETag = require('./utils').compileETag;
25 var compileQueryParser = require('./utils').compileQueryParser;
26 var compileTrust = require('./utils').compileTrust;
27 var deprecate = require('depd')('express');
28 var flatten = require('array-flatten');
29 var merge = require('utils-merge');
30 var resolve = require('path').resolve;
31 var setPrototypeOf = require('setprototypeof')
33 /**
34  * Module variables.
35  * @private
36  */
38 var hasOwnProperty = Object.prototype.hasOwnProperty
39 var slice = Array.prototype.slice;
41 /**
42  * Application prototype.
43  */
45 var app = exports = module.exports = {};
47 /**
48  * Variable for trust proxy inheritance back-compat
49  * @private
50  */
52 var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
54 /**
55  * Initialize the server.
56  *
57  *   - setup default configuration
58  *   - setup default middleware
59  *   - setup route reflection methods
60  *
61  * @private
62  */
64 app.init = function init() {
65   this.cache = {};
66   this.engines = {};
67   this.settings = {};
69   this.defaultConfiguration();
72 /**
73  * Initialize application configuration.
74  * @private
75  */
77 app.defaultConfiguration = function defaultConfiguration() {
78   var env = process.env.NODE_ENV || 'development';
80   // default settings
81   this.enable('x-powered-by');
82   this.set('etag', 'weak');
83   this.set('env', env);
84   this.set('query parser', 'extended');
85   this.set('subdomain offset', 2);
86   this.set('trust proxy', false);
88   // trust proxy inherit back-compat
89   Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
90     configurable: true,
91     value: true
92   });
94   debug('booting in %s mode', env);
96   this.on('mount', function onmount(parent) {
97     // inherit trust proxy
98     if (this.settings[trustProxyDefaultSymbol] === true
99       && typeof parent.settings['trust proxy fn'] === 'function') {
100       delete this.settings['trust proxy'];
101       delete this.settings['trust proxy fn'];
102     }
104     // inherit protos
105     setPrototypeOf(this.request, parent.request)
106     setPrototypeOf(this.response, parent.response)
107     setPrototypeOf(this.engines, parent.engines)
108     setPrototypeOf(this.settings, parent.settings)
109   });
111   // setup locals
112   this.locals = Object.create(null);
114   // top-most app is mounted at /
115   this.mountpath = '/';
117   // default locals
118   this.locals.settings = this.settings;
120   // default configuration
121   this.set('view', View);
122   this.set('views', resolve('views'));
123   this.set('jsonp callback name', 'callback');
125   if (env === 'production') {
126     this.enable('view cache');
127   }
129   Object.defineProperty(this, 'router', {
130     get: function() {
131       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.');
132     }
133   });
137  * lazily adds the base router if it has not yet been added.
139  * We cannot add the base router in the defaultConfiguration because
140  * it reads app settings which might be set after that has run.
142  * @private
143  */
144 app.lazyrouter = function lazyrouter() {
145   if (!this._router) {
146     this._router = new Router({
147       caseSensitive: this.enabled('case sensitive routing'),
148       strict: this.enabled('strict routing')
149     });
151     this._router.use(query(this.get('query parser fn')));
152     this._router.use(middleware.init(this));
153   }
157  * Dispatch a req, res pair into the application. Starts pipeline processing.
159  * If no callback is provided, then default error handlers will respond
160  * in the event of an error bubbling through the stack.
162  * @private
163  */
165 app.handle = function handle(req, res, callback) {
166   var router = this._router;
168   // final handler
169   var done = callback || finalhandler(req, res, {
170     env: this.get('env'),
171     onerror: logerror.bind(this)
172   });
174   // no routes
175   if (!router) {
176     debug('no routes defined on app');
177     done();
178     return;
179   }
181   router.handle(req, res, done);
185  * Proxy `Router#use()` to add middleware to the app router.
186  * See Router#use() documentation for details.
188  * If the _fn_ parameter is an express app, then it will be
189  * mounted at the _route_ specified.
191  * @public
192  */
194 app.use = function use(fn) {
195   var offset = 0;
196   var path = '/';
198   // default path to '/'
199   // disambiguate app.use([fn])
200   if (typeof fn !== 'function') {
201     var arg = fn;
203     while (Array.isArray(arg) && arg.length !== 0) {
204       arg = arg[0];
205     }
207     // first arg is the path
208     if (typeof arg !== 'function') {
209       offset = 1;
210       path = fn;
211     }
212   }
214   var fns = flatten(slice.call(arguments, offset));
216   if (fns.length === 0) {
217     throw new TypeError('app.use() requires a middleware function')
218   }
220   // setup router
221   this.lazyrouter();
222   var router = this._router;
224   fns.forEach(function (fn) {
225     // non-express app
226     if (!fn || !fn.handle || !fn.set) {
227       return router.use(path, fn);
228     }
230     debug('.use app under %s', path);
231     fn.mountpath = path;
232     fn.parent = this;
234     // restore .app property on req and res
235     router.use(path, function mounted_app(req, res, next) {
236       var orig = req.app;
237       fn.handle(req, res, function (err) {
238         setPrototypeOf(req, orig.request)
239         setPrototypeOf(res, orig.response)
240         next(err);
241       });
242     });
244     // mounted an app
245     fn.emit('mount', this);
246   }, this);
248   return this;
252  * Proxy to the app `Router#route()`
253  * Returns a new `Route` instance for the _path_.
255  * Routes are isolated middleware stacks for specific paths.
256  * See the Route api docs for details.
258  * @public
259  */
261 app.route = function route(path) {
262   this.lazyrouter();
263   return this._router.route(path);
267  * Register the given template engine callback `fn`
268  * as `ext`.
270  * By default will `require()` the engine based on the
271  * file extension. For example if you try to render
272  * a "foo.ejs" file Express will invoke the following internally:
274  *     app.engine('ejs', require('ejs').__express);
276  * For engines that do not provide `.__express` out of the box,
277  * or if you wish to "map" a different extension to the template engine
278  * you may use this method. For example mapping the EJS template engine to
279  * ".html" files:
281  *     app.engine('html', require('ejs').renderFile);
283  * In this case EJS provides a `.renderFile()` method with
284  * the same signature that Express expects: `(path, options, callback)`,
285  * though note that it aliases this method as `ejs.__express` internally
286  * so if you're using ".ejs" extensions you don't need to do anything.
288  * Some template engines do not follow this convention, the
289  * [Consolidate.js](https://github.com/tj/consolidate.js)
290  * library was created to map all of node's popular template
291  * engines to follow this convention, thus allowing them to
292  * work seamlessly within Express.
294  * @param {String} ext
295  * @param {Function} fn
296  * @return {app} for chaining
297  * @public
298  */
300 app.engine = function engine(ext, fn) {
301   if (typeof fn !== 'function') {
302     throw new Error('callback function required');
303   }
305   // get file extension
306   var extension = ext[0] !== '.'
307     ? '.' + ext
308     : ext;
310   // store engine
311   this.engines[extension] = fn;
313   return this;
317  * Proxy to `Router#param()` with one added api feature. The _name_ parameter
318  * can be an array of names.
320  * See the Router#param() docs for more details.
322  * @param {String|Array} name
323  * @param {Function} fn
324  * @return {app} for chaining
325  * @public
326  */
328 app.param = function param(name, fn) {
329   this.lazyrouter();
331   if (Array.isArray(name)) {
332     for (var i = 0; i < name.length; i++) {
333       this.param(name[i], fn);
334     }
336     return this;
337   }
339   this._router.param(name, fn);
341   return this;
345  * Assign `setting` to `val`, or return `setting`'s value.
347  *    app.set('foo', 'bar');
348  *    app.set('foo');
349  *    // => "bar"
351  * Mounted servers inherit their parent server's settings.
353  * @param {String} setting
354  * @param {*} [val]
355  * @return {Server} for chaining
356  * @public
357  */
359 app.set = function set(setting, val) {
360   if (arguments.length === 1) {
361     // app.get(setting)
362     var settings = this.settings
364     while (settings && settings !== Object.prototype) {
365       if (hasOwnProperty.call(settings, setting)) {
366         return settings[setting]
367       }
369       settings = Object.getPrototypeOf(settings)
370     }
372     return undefined
373   }
375   debug('set "%s" to %o', setting, val);
377   // set value
378   this.settings[setting] = val;
380   // trigger matched settings
381   switch (setting) {
382     case 'etag':
383       this.set('etag fn', compileETag(val));
384       break;
385     case 'query parser':
386       this.set('query parser fn', compileQueryParser(val));
387       break;
388     case 'trust proxy':
389       this.set('trust proxy fn', compileTrust(val));
391       // trust proxy inherit back-compat
392       Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
393         configurable: true,
394         value: false
395       });
397       break;
398   }
400   return this;
404  * Return the app's absolute pathname
405  * based on the parent(s) that have
406  * mounted it.
408  * For example if the application was
409  * mounted as "/admin", which itself
410  * was mounted as "/blog" then the
411  * return value would be "/blog/admin".
413  * @return {String}
414  * @private
415  */
417 app.path = function path() {
418   return this.parent
419     ? this.parent.path() + this.mountpath
420     : '';
424  * Check if `setting` is enabled (truthy).
426  *    app.enabled('foo')
427  *    // => false
429  *    app.enable('foo')
430  *    app.enabled('foo')
431  *    // => true
433  * @param {String} setting
434  * @return {Boolean}
435  * @public
436  */
438 app.enabled = function enabled(setting) {
439   return Boolean(this.set(setting));
443  * Check if `setting` is disabled.
445  *    app.disabled('foo')
446  *    // => true
448  *    app.enable('foo')
449  *    app.disabled('foo')
450  *    // => false
452  * @param {String} setting
453  * @return {Boolean}
454  * @public
455  */
457 app.disabled = function disabled(setting) {
458   return !this.set(setting);
462  * Enable `setting`.
464  * @param {String} setting
465  * @return {app} for chaining
466  * @public
467  */
469 app.enable = function enable(setting) {
470   return this.set(setting, true);
474  * Disable `setting`.
476  * @param {String} setting
477  * @return {app} for chaining
478  * @public
479  */
481 app.disable = function disable(setting) {
482   return this.set(setting, false);
486  * Delegate `.VERB(...)` calls to `router.VERB(...)`.
487  */
489 methods.forEach(function(method){
490   app[method] = function(path){
491     if (method === 'get' && arguments.length === 1) {
492       // app.get(setting)
493       return this.set(path);
494     }
496     this.lazyrouter();
498     var route = this._router.route(path);
499     route[method].apply(route, slice.call(arguments, 1));
500     return this;
501   };
505  * Special-cased "all" method, applying the given route `path`,
506  * middleware, and callback to _every_ HTTP method.
508  * @param {String} path
509  * @param {Function} ...
510  * @return {app} for chaining
511  * @public
512  */
514 app.all = function all(path) {
515   this.lazyrouter();
517   var route = this._router.route(path);
518   var args = slice.call(arguments, 1);
520   for (var i = 0; i < methods.length; i++) {
521     route[methods[i]].apply(route, args);
522   }
524   return this;
527 // del -> delete alias
529 app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
532  * Render the given view `name` name with `options`
533  * and a callback accepting an error and the
534  * rendered template string.
536  * Example:
538  *    app.render('email', { name: 'Tobi' }, function(err, html){
539  *      // ...
540  *    })
542  * @param {String} name
543  * @param {Object|Function} options or fn
544  * @param {Function} callback
545  * @public
546  */
548 app.render = function render(name, options, callback) {
549   var cache = this.cache;
550   var done = callback;
551   var engines = this.engines;
552   var opts = options;
553   var renderOptions = {};
554   var view;
556   // support callback function as second arg
557   if (typeof options === 'function') {
558     done = options;
559     opts = {};
560   }
562   // merge app.locals
563   merge(renderOptions, this.locals);
565   // merge options._locals
566   if (opts._locals) {
567     merge(renderOptions, opts._locals);
568   }
570   // merge options
571   merge(renderOptions, opts);
573   // set .cache unless explicitly provided
574   if (renderOptions.cache == null) {
575     renderOptions.cache = this.enabled('view cache');
576   }
578   // primed cache
579   if (renderOptions.cache) {
580     view = cache[name];
581   }
583   // view
584   if (!view) {
585     var View = this.get('view');
587     view = new View(name, {
588       defaultEngine: this.get('view engine'),
589       root: this.get('views'),
590       engines: engines
591     });
593     if (!view.path) {
594       var dirs = Array.isArray(view.root) && view.root.length > 1
595         ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
596         : 'directory "' + view.root + '"'
597       var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
598       err.view = view;
599       return done(err);
600     }
602     // prime the cache
603     if (renderOptions.cache) {
604       cache[name] = view;
605     }
606   }
608   // render
609   tryRender(view, renderOptions, done);
613  * Listen for connections.
615  * A node `http.Server` is returned, with this
616  * application (which is a `Function`) as its
617  * callback. If you wish to create both an HTTP
618  * and HTTPS server you may do so with the "http"
619  * and "https" modules as shown here:
621  *    var http = require('http')
622  *      , https = require('https')
623  *      , express = require('express')
624  *      , app = express();
626  *    http.createServer(app).listen(80);
627  *    https.createServer({ ... }, app).listen(443);
629  * @return {http.Server}
630  * @public
631  */
633 app.listen = function listen() {
634   var server = http.createServer(this);
635   return server.listen.apply(server, arguments);
639  * Log error using console.error.
641  * @param {Error} err
642  * @private
643  */
645 function logerror(err) {
646   /* istanbul ignore next */
647   if (this.get('env') !== 'test') console.error(err.stack || err.toString());
651  * Try rendering a view.
652  * @private
653  */
655 function tryRender(view, options, callback) {
656   try {
657     view.render(options, callback);
658   } catch (err) {
659     callback(err);
660   }