application.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /**
  2. * Module dependencies.
  3. */
  4. var connect = require('connect')
  5. , Router = require('./router')
  6. , methods = require('methods')
  7. , middleware = require('./middleware')
  8. , debug = require('debug')('express:application')
  9. , locals = require('./utils').locals
  10. , View = require('./view')
  11. , utils = connect.utils
  12. , path = require('path')
  13. , http = require('http')
  14. , join = path.join;
  15. /**
  16. * Application prototype.
  17. */
  18. var app = exports = module.exports = {};
  19. /**
  20. * Initialize the server.
  21. *
  22. * - setup default configuration
  23. * - setup default middleware
  24. * - setup route reflection methods
  25. *
  26. * @api private
  27. */
  28. app.init = function(){
  29. this.cache = {};
  30. this.settings = {};
  31. this.engines = {};
  32. this.defaultConfiguration();
  33. };
  34. /**
  35. * Initialize application configuration.
  36. *
  37. * @api private
  38. */
  39. app.defaultConfiguration = function(){
  40. // default settings
  41. this.enable('x-powered-by');
  42. this.set('env', process.env.NODE_ENV || 'development');
  43. this.set('subdomain offset', 2);
  44. debug('booting in %s mode', this.get('env'));
  45. // implicit middleware
  46. this.use(connect.query());
  47. this.use(middleware.init(this));
  48. // inherit protos
  49. this.on('mount', function(parent){
  50. this.request.__proto__ = parent.request;
  51. this.response.__proto__ = parent.response;
  52. this.engines.__proto__ = parent.engines;
  53. this.settings.__proto__ = parent.settings;
  54. });
  55. // router
  56. this._router = new Router(this);
  57. this.routes = this._router.map;
  58. this.__defineGetter__('router', function(){
  59. this._usedRouter = true;
  60. this._router.caseSensitive = this.enabled('case sensitive routing');
  61. this._router.strict = this.enabled('strict routing');
  62. return this._router.middleware;
  63. });
  64. // setup locals
  65. this.locals = locals(this);
  66. // default locals
  67. this.locals.settings = this.settings;
  68. // default configuration
  69. this.set('view', View);
  70. this.set('views', process.cwd() + '/views');
  71. this.set('jsonp callback name', 'callback');
  72. this.configure('development', function(){
  73. this.set('json spaces', 2);
  74. });
  75. this.configure('production', function(){
  76. this.enable('view cache');
  77. });
  78. };
  79. /**
  80. * Proxy `connect#use()` to apply settings to
  81. * mounted applications.
  82. *
  83. * @param {String|Function|Server} route
  84. * @param {Function|Server} fn
  85. * @return {app} for chaining
  86. * @api public
  87. */
  88. app.use = function(route, fn){
  89. var app;
  90. // default route to '/'
  91. if ('string' != typeof route) fn = route, route = '/';
  92. // express app
  93. if (fn.handle && fn.set) app = fn;
  94. // restore .app property on req and res
  95. if (app) {
  96. app.route = route;
  97. fn = function(req, res, next) {
  98. var orig = req.app;
  99. app.handle(req, res, function(err){
  100. req.__proto__ = orig.request;
  101. res.__proto__ = orig.response;
  102. next(err);
  103. });
  104. };
  105. }
  106. connect.proto.use.call(this, route, fn);
  107. // mounted an app
  108. if (app) {
  109. app.parent = this;
  110. app.emit('mount', this);
  111. }
  112. return this;
  113. };
  114. /**
  115. * Register the given template engine callback `fn`
  116. * as `ext`.
  117. *
  118. * By default will `require()` the engine based on the
  119. * file extension. For example if you try to render
  120. * a "foo.jade" file Express will invoke the following internally:
  121. *
  122. * app.engine('jade', require('jade').__express);
  123. *
  124. * For engines that do not provide `.__express` out of the box,
  125. * or if you wish to "map" a different extension to the template engine
  126. * you may use this method. For example mapping the EJS template engine to
  127. * ".html" files:
  128. *
  129. * app.engine('html', require('ejs').renderFile);
  130. *
  131. * In this case EJS provides a `.renderFile()` method with
  132. * the same signature that Express expects: `(path, options, callback)`,
  133. * though note that it aliases this method as `ejs.__express` internally
  134. * so if you're using ".ejs" extensions you dont need to do anything.
  135. *
  136. * Some template engines do not follow this convention, the
  137. * [Consolidate.js](https://github.com/visionmedia/consolidate.js)
  138. * library was created to map all of node's popular template
  139. * engines to follow this convention, thus allowing them to
  140. * work seamlessly within Express.
  141. *
  142. * @param {String} ext
  143. * @param {Function} fn
  144. * @return {app} for chaining
  145. * @api public
  146. */
  147. app.engine = function(ext, fn){
  148. if ('function' != typeof fn) throw new Error('callback function required');
  149. if ('.' != ext[0]) ext = '.' + ext;
  150. this.engines[ext] = fn;
  151. return this;
  152. };
  153. /**
  154. * Map the given param placeholder `name`(s) to the given callback(s).
  155. *
  156. * Parameter mapping is used to provide pre-conditions to routes
  157. * which use normalized placeholders. For example a _:user_id_ parameter
  158. * could automatically load a user's information from the database without
  159. * any additional code,
  160. *
  161. * The callback uses the samesignature as middleware, the only differencing
  162. * being that the value of the placeholder is passed, in this case the _id_
  163. * of the user. Once the `next()` function is invoked, just like middleware
  164. * it will continue on to execute the route, or subsequent parameter functions.
  165. *
  166. * app.param('user_id', function(req, res, next, id){
  167. * User.find(id, function(err, user){
  168. * if (err) {
  169. * next(err);
  170. * } else if (user) {
  171. * req.user = user;
  172. * next();
  173. * } else {
  174. * next(new Error('failed to load user'));
  175. * }
  176. * });
  177. * });
  178. *
  179. * @param {String|Array} name
  180. * @param {Function} fn
  181. * @return {app} for chaining
  182. * @api public
  183. */
  184. app.param = function(name, fn){
  185. var self = this
  186. , fns = [].slice.call(arguments, 1);
  187. // array
  188. if (Array.isArray(name)) {
  189. name.forEach(function(name){
  190. fns.forEach(function(fn){
  191. self.param(name, fn);
  192. });
  193. });
  194. // param logic
  195. } else if ('function' == typeof name) {
  196. this._router.param(name);
  197. // single
  198. } else {
  199. if (':' == name[0]) name = name.substr(1);
  200. fns.forEach(function(fn){
  201. self._router.param(name, fn);
  202. });
  203. }
  204. return this;
  205. };
  206. /**
  207. * Assign `setting` to `val`, or return `setting`'s value.
  208. *
  209. * app.set('foo', 'bar');
  210. * app.get('foo');
  211. * // => "bar"
  212. *
  213. * Mounted servers inherit their parent server's settings.
  214. *
  215. * @param {String} setting
  216. * @param {String} val
  217. * @return {Server} for chaining
  218. * @api public
  219. */
  220. app.set = function(setting, val){
  221. if (1 == arguments.length) {
  222. return this.settings[setting];
  223. } else {
  224. this.settings[setting] = val;
  225. return this;
  226. }
  227. };
  228. /**
  229. * Return the app's absolute pathname
  230. * based on the parent(s) that have
  231. * mounted it.
  232. *
  233. * For example if the application was
  234. * mounted as "/admin", which itself
  235. * was mounted as "/blog" then the
  236. * return value would be "/blog/admin".
  237. *
  238. * @return {String}
  239. * @api private
  240. */
  241. app.path = function(){
  242. return this.parent
  243. ? this.parent.path() + this.route
  244. : '';
  245. };
  246. /**
  247. * Check if `setting` is enabled (truthy).
  248. *
  249. * app.enabled('foo')
  250. * // => false
  251. *
  252. * app.enable('foo')
  253. * app.enabled('foo')
  254. * // => true
  255. *
  256. * @param {String} setting
  257. * @return {Boolean}
  258. * @api public
  259. */
  260. app.enabled = function(setting){
  261. return !!this.set(setting);
  262. };
  263. /**
  264. * Check if `setting` is disabled.
  265. *
  266. * app.disabled('foo')
  267. * // => true
  268. *
  269. * app.enable('foo')
  270. * app.disabled('foo')
  271. * // => false
  272. *
  273. * @param {String} setting
  274. * @return {Boolean}
  275. * @api public
  276. */
  277. app.disabled = function(setting){
  278. return !this.set(setting);
  279. };
  280. /**
  281. * Enable `setting`.
  282. *
  283. * @param {String} setting
  284. * @return {app} for chaining
  285. * @api public
  286. */
  287. app.enable = function(setting){
  288. return this.set(setting, true);
  289. };
  290. /**
  291. * Disable `setting`.
  292. *
  293. * @param {String} setting
  294. * @return {app} for chaining
  295. * @api public
  296. */
  297. app.disable = function(setting){
  298. return this.set(setting, false);
  299. };
  300. /**
  301. * Configure callback for zero or more envs,
  302. * when no `env` is specified that callback will
  303. * be invoked for all environments. Any combination
  304. * can be used multiple times, in any order desired.
  305. *
  306. * Examples:
  307. *
  308. * app.configure(function(){
  309. * // executed for all envs
  310. * });
  311. *
  312. * app.configure('stage', function(){
  313. * // executed staging env
  314. * });
  315. *
  316. * app.configure('stage', 'production', function(){
  317. * // executed for stage and production
  318. * });
  319. *
  320. * Note:
  321. *
  322. * These callbacks are invoked immediately, and
  323. * are effectively sugar for the following:
  324. *
  325. * var env = process.env.NODE_ENV || 'development';
  326. *
  327. * switch (env) {
  328. * case 'development':
  329. * ...
  330. * break;
  331. * case 'stage':
  332. * ...
  333. * break;
  334. * case 'production':
  335. * ...
  336. * break;
  337. * }
  338. *
  339. * @param {String} env...
  340. * @param {Function} fn
  341. * @return {app} for chaining
  342. * @api public
  343. */
  344. app.configure = function(env, fn){
  345. var envs = 'all'
  346. , args = [].slice.call(arguments);
  347. fn = args.pop();
  348. if (args.length) envs = args;
  349. if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
  350. return this;
  351. };
  352. /**
  353. * Delegate `.VERB(...)` calls to `router.VERB(...)`.
  354. */
  355. methods.forEach(function(method){
  356. app[method] = function(path){
  357. if ('get' == method && 1 == arguments.length) return this.set(path);
  358. // deprecated
  359. if (Array.isArray(path)) {
  360. console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0');
  361. }
  362. // if no router attached yet, attach the router
  363. if (!this._usedRouter) this.use(this.router);
  364. // setup route
  365. this._router[method].apply(this._router, arguments);
  366. return this;
  367. };
  368. });
  369. /**
  370. * Special-cased "all" method, applying the given route `path`,
  371. * middleware, and callback to _every_ HTTP method.
  372. *
  373. * @param {String} path
  374. * @param {Function} ...
  375. * @return {app} for chaining
  376. * @api public
  377. */
  378. app.all = function(path){
  379. var args = arguments;
  380. methods.forEach(function(method){
  381. app[method].apply(this, args);
  382. }, this);
  383. return this;
  384. };
  385. // del -> delete alias
  386. app.del = app.delete;
  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. , cache = this.cache
  406. , engines = this.engines
  407. , view;
  408. // support callback function as second arg
  409. if ('function' == typeof options) {
  410. fn = options, options = {};
  411. }
  412. // merge app.locals
  413. utils.merge(opts, this.locals);
  414. // merge options._locals
  415. if (options._locals) utils.merge(opts, options._locals);
  416. // merge options
  417. utils.merge(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 + '"');
  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. };