application.js 12 KB

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