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.

644 lines
14 KiB

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