You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

643 lines
14 KiB

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