application.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /**
  2. * Module dependencies.
  3. */
  4. var mixin = require('utils-merge');
  5. var escapeHtml = require('escape-html');
  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('./utils').deprecate;
  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 env = this.get('env');
  104. this._router.handle(req, res, function(err) {
  105. if (done) {
  106. return done(err);
  107. }
  108. // unhandled error
  109. if (err) {
  110. // default to 500
  111. if (res.statusCode < 400) res.statusCode = 500;
  112. debug('default %s', res.statusCode);
  113. // respect err.status
  114. if (err.status) res.statusCode = err.status;
  115. // production gets a basic error message
  116. var msg = 'production' == env
  117. ? http.STATUS_CODES[res.statusCode]
  118. : err.stack || err.toString();
  119. msg = escapeHtml(msg);
  120. // log to stderr in a non-test env
  121. if ('test' != env) console.error(err.stack || err.toString());
  122. if (res.headersSent) return req.socket.destroy();
  123. res.setHeader('Content-Type', 'text/html');
  124. res.setHeader('Content-Length', Buffer.byteLength(msg));
  125. if ('HEAD' == req.method) return res.end();
  126. res.end(msg);
  127. return;
  128. }
  129. // 404
  130. debug('default 404');
  131. res.statusCode = 404;
  132. res.setHeader('Content-Type', 'text/html');
  133. if ('HEAD' == req.method) return res.end();
  134. res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
  135. });
  136. };
  137. /**
  138. * Proxy `Router#use()` to add middleware to the app router.
  139. * See Router#use() documentation for details.
  140. *
  141. * If the _fn_ parameter is an express app, then it will be
  142. * mounted at the _route_ specified.
  143. *
  144. * @api public
  145. */
  146. app.use = function(route, fn){
  147. var mount_app;
  148. // default route to '/'
  149. if ('string' != typeof route) fn = route, route = '/';
  150. // express app
  151. if (fn.handle && fn.set) mount_app = fn;
  152. // restore .app property on req and res
  153. if (mount_app) {
  154. debug('.use app under %s', route);
  155. mount_app.mountpath = route;
  156. fn = function(req, res, next) {
  157. var orig = req.app;
  158. mount_app.handle(req, res, function(err) {
  159. req.__proto__ = orig.request;
  160. res.__proto__ = orig.response;
  161. next(err);
  162. });
  163. };
  164. }
  165. this.lazyrouter();
  166. this._router.use(route, fn);
  167. // mounted an app
  168. if (mount_app) {
  169. mount_app.parent = this;
  170. mount_app.emit('mount', this);
  171. }
  172. return this;
  173. };
  174. /**
  175. * Proxy to the app `Router#route()`
  176. * Returns a new `Route` instance for the _path_.
  177. *
  178. * Routes are isolated middleware stacks for specific paths.
  179. * See the Route api docs for details.
  180. *
  181. * @api public
  182. */
  183. app.route = function(path){
  184. this.lazyrouter();
  185. return this._router.route(path);
  186. };
  187. /**
  188. * Register the given template engine callback `fn`
  189. * as `ext`.
  190. *
  191. * By default will `require()` the engine based on the
  192. * file extension. For example if you try to render
  193. * a "foo.jade" file Express will invoke the following internally:
  194. *
  195. * app.engine('jade', require('jade').__express);
  196. *
  197. * For engines that do not provide `.__express` out of the box,
  198. * or if you wish to "map" a different extension to the template engine
  199. * you may use this method. For example mapping the EJS template engine to
  200. * ".html" files:
  201. *
  202. * app.engine('html', require('ejs').renderFile);
  203. *
  204. * In this case EJS provides a `.renderFile()` method with
  205. * the same signature that Express expects: `(path, options, callback)`,
  206. * though note that it aliases this method as `ejs.__express` internally
  207. * so if you're using ".ejs" extensions you dont need to do anything.
  208. *
  209. * Some template engines do not follow this convention, the
  210. * [Consolidate.js](https://github.com/visionmedia/consolidate.js)
  211. * library was created to map all of node's popular template
  212. * engines to follow this convention, thus allowing them to
  213. * work seamlessly within Express.
  214. *
  215. * @param {String} ext
  216. * @param {Function} fn
  217. * @return {app} for chaining
  218. * @api public
  219. */
  220. app.engine = function(ext, fn){
  221. if ('function' != typeof fn) throw new Error('callback function required');
  222. if ('.' != ext[0]) ext = '.' + ext;
  223. this.engines[ext] = fn;
  224. return this;
  225. };
  226. /**
  227. * Proxy to `Router#param()` with one added api feature. The _name_ parameter
  228. * can be an array of names.
  229. *
  230. * See the Router#param() docs for more details.
  231. *
  232. * @param {String|Array} name
  233. * @param {Function} fn
  234. * @return {app} for chaining
  235. * @api public
  236. */
  237. app.param = function(name, fn){
  238. var self = this;
  239. self.lazyrouter();
  240. if (Array.isArray(name)) {
  241. name.forEach(function(key) {
  242. self.param(key, fn);
  243. });
  244. return this;
  245. }
  246. self._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 'trust proxy':
  277. debug('compile trust proxy %s', val);
  278. this.set('trust proxy fn', compileTrust(val));
  279. break;
  280. }
  281. return this;
  282. };
  283. /**
  284. * Return the app's absolute pathname
  285. * based on the parent(s) that have
  286. * mounted it.
  287. *
  288. * For example if the application was
  289. * mounted as "/admin", which itself
  290. * was mounted as "/blog" then the
  291. * return value would be "/blog/admin".
  292. *
  293. * @return {String}
  294. * @api private
  295. */
  296. app.path = function(){
  297. return this.parent
  298. ? this.parent.path() + this.mountpath
  299. : '';
  300. };
  301. /**
  302. * Check if `setting` is enabled (truthy).
  303. *
  304. * app.enabled('foo')
  305. * // => false
  306. *
  307. * app.enable('foo')
  308. * app.enabled('foo')
  309. * // => true
  310. *
  311. * @param {String} setting
  312. * @return {Boolean}
  313. * @api public
  314. */
  315. app.enabled = function(setting){
  316. return !!this.set(setting);
  317. };
  318. /**
  319. * Check if `setting` is disabled.
  320. *
  321. * app.disabled('foo')
  322. * // => true
  323. *
  324. * app.enable('foo')
  325. * app.disabled('foo')
  326. * // => false
  327. *
  328. * @param {String} setting
  329. * @return {Boolean}
  330. * @api public
  331. */
  332. app.disabled = function(setting){
  333. return !this.set(setting);
  334. };
  335. /**
  336. * Enable `setting`.
  337. *
  338. * @param {String} setting
  339. * @return {app} for chaining
  340. * @api public
  341. */
  342. app.enable = function(setting){
  343. return this.set(setting, true);
  344. };
  345. /**
  346. * Disable `setting`.
  347. *
  348. * @param {String} setting
  349. * @return {app} for chaining
  350. * @api public
  351. */
  352. app.disable = function(setting){
  353. return this.set(setting, false);
  354. };
  355. /**
  356. * Delegate `.VERB(...)` calls to `router.VERB(...)`.
  357. */
  358. methods.forEach(function(method){
  359. app[method] = function(path){
  360. if ('get' == method && 1 == arguments.length) return this.set(path);
  361. this.lazyrouter();
  362. var route = this._router.route(path);
  363. route[method].apply(route, [].slice.call(arguments, 1));
  364. return this;
  365. };
  366. });
  367. /**
  368. * Special-cased "all" method, applying the given route `path`,
  369. * middleware, and callback to _every_ HTTP method.
  370. *
  371. * @param {String} path
  372. * @param {Function} ...
  373. * @return {app} for chaining
  374. * @api public
  375. */
  376. app.all = function(path){
  377. this.lazyrouter();
  378. var route = this._router.route(path);
  379. var args = [].slice.call(arguments, 1);
  380. methods.forEach(function(method){
  381. route[method].apply(route, args);
  382. });
  383. return this;
  384. };
  385. // del -> delete alias
  386. app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
  387. /**
  388. * Render the given view `name` name with `options`
  389. * and a callback accepting an error and the
  390. * rendered template string.
  391. *
  392. * Example:
  393. *
  394. * app.render('email', { name: 'Tobi' }, function(err, html){
  395. * // ...
  396. * })
  397. *
  398. * @param {String} name
  399. * @param {String|Function} options or fn
  400. * @param {Function} fn
  401. * @api public
  402. */
  403. app.render = function(name, options, fn){
  404. var opts = {};
  405. var cache = this.cache;
  406. var engines = this.engines;
  407. var view;
  408. // support callback function as second arg
  409. if ('function' == typeof options) {
  410. fn = options, options = {};
  411. }
  412. // merge app.locals
  413. mixin(opts, this.locals);
  414. // merge options._locals
  415. if (options._locals) mixin(opts, options._locals);
  416. // merge options
  417. mixin(opts, options);
  418. // set .cache unless explicitly provided
  419. opts.cache = null == opts.cache
  420. ? this.enabled('view cache')
  421. : opts.cache;
  422. // primed cache
  423. if (opts.cache) view = cache[name];
  424. // view
  425. if (!view) {
  426. view = new (this.get('view'))(name, {
  427. defaultEngine: this.get('view engine'),
  428. root: this.get('views'),
  429. engines: engines
  430. });
  431. if (!view.path) {
  432. var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
  433. err.view = view;
  434. return fn(err);
  435. }
  436. // prime the cache
  437. if (opts.cache) cache[name] = view;
  438. }
  439. // render
  440. try {
  441. view.render(opts, fn);
  442. } catch (err) {
  443. fn(err);
  444. }
  445. };
  446. /**
  447. * Listen for connections.
  448. *
  449. * A node `http.Server` is returned, with this
  450. * application (which is a `Function`) as its
  451. * callback. If you wish to create both an HTTP
  452. * and HTTPS server you may do so with the "http"
  453. * and "https" modules as shown here:
  454. *
  455. * var http = require('http')
  456. * , https = require('https')
  457. * , express = require('express')
  458. * , app = express();
  459. *
  460. * http.createServer(app).listen(80);
  461. * https.createServer({ ... }, app).listen(443);
  462. *
  463. * @return {http.Server}
  464. * @api public
  465. */
  466. app.listen = function(){
  467. var server = http.createServer(this);
  468. return server.listen.apply(server, arguments);
  469. };