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.

571 lines
12 KiB

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