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.

536 lines
12 KiB

  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.app = res.app = orig;
  101. req.__proto__ = orig.request;
  102. res.__proto__ = orig.response;
  103. next(err);
  104. });
  105. };
  106. }
  107. connect.proto.use.call(this, route, fn);
  108. // mounted an app
  109. if (app) {
  110. app.parent = this;
  111. app.emit('mount', this);
  112. }
  113. return this;
  114. };
  115. /**
  116. * Register the given template engine callback `fn`
  117. * as `ext`.
  118. *
  119. * By default will `require()` the engine based on the
  120. * file extension. For example if you try to render
  121. * a "foo.jade" file Express will invoke the following internally:
  122. *
  123. * app.engine('jade', require('jade').__express);
  124. *
  125. * For engines that do not provide `.__express` out of the box,
  126. * or if you wish to "map" a different extension to the template engine
  127. * you may use this method. For example mapping the EJS template engine to
  128. * ".html" files:
  129. *
  130. * app.engine('html', require('ejs').renderFile);
  131. *
  132. * In this case EJS provides a `.renderFile()` method with
  133. * the same signature that Express expects: `(path, options, callback)`,
  134. * though note that it aliases this method as `ejs.__express` internally
  135. * so if you're using ".ejs" extensions you dont need to do anything.
  136. *
  137. * Some template engines do not follow this convention, the
  138. * [Consolidate.js](https://github.com/visionmedia/consolidate.js)
  139. * library was created to map all of node's popular template
  140. * engines to follow this convention, thus allowing them to
  141. * work seamlessly within Express.
  142. *
  143. * @param {String} ext
  144. * @param {Function} fn
  145. * @return {app} for chaining
  146. * @api public
  147. */
  148. app.engine = function(ext, fn){
  149. if ('function' != typeof fn) throw new Error('callback function required');
  150. if ('.' != ext[0]) ext = '.' + ext;
  151. this.engines[ext] = fn;
  152. return this;
  153. };
  154. /**
  155. * Map the given param placeholder `name`(s) to the given callback(s).
  156. *
  157. * Parameter mapping is used to provide pre-conditions to routes
  158. * which use normalized placeholders. For example a _:user_id_ parameter
  159. * could automatically load a user's information from the database without
  160. * any additional code,
  161. *
  162. * The callback uses the samesignature as middleware, the only differencing
  163. * being that the value of the placeholder is passed, in this case the _id_
  164. * of the user. Once the `next()` function is invoked, just like middleware
  165. * it will continue on to execute the route, or subsequent parameter functions.
  166. *
  167. * app.param('user_id', function(req, res, next, id){
  168. * User.find(id, function(err, user){
  169. * if (err) {
  170. * next(err);
  171. * } else if (user) {
  172. * req.user = user;
  173. * next();
  174. * } else {
  175. * next(new Error('failed to load user'));
  176. * }
  177. * });
  178. * });
  179. *
  180. * @param {String|Array} name
  181. * @param {Function} fn
  182. * @return {app} for chaining
  183. * @api public
  184. */
  185. app.param = function(name, fn){
  186. var self = this
  187. , fns = [].slice.call(arguments, 1);
  188. // array
  189. if (Array.isArray(name)) {
  190. name.forEach(function(name){
  191. fns.forEach(function(fn){
  192. self.param(name, fn);
  193. });
  194. });
  195. // param logic
  196. } else if ('function' == typeof name) {
  197. this._router.param(name);
  198. // single
  199. } else {
  200. if (':' == name[0]) name = name.substr(1);
  201. fns.forEach(function(fn){
  202. self._router.param(name, fn);
  203. });
  204. }
  205. return this;
  206. };
  207. /**
  208. * Assign `setting` to `val`, or return `setting`'s value.
  209. *
  210. * app.set('foo', 'bar');
  211. * app.get('foo');
  212. * // => "bar"
  213. *
  214. * Mounted servers inherit their parent server's settings.
  215. *
  216. * @param {String} setting
  217. * @param {String} val
  218. * @return {Server} for chaining
  219. * @api public
  220. */
  221. app.set = function(setting, val){
  222. if (1 == arguments.length) {
  223. return this.settings[setting];
  224. } else {
  225. this.settings[setting] = val;
  226. return this;
  227. }
  228. };
  229. /**
  230. * Return the app's absolute pathname
  231. * based on the parent(s) that have
  232. * mounted it.
  233. *
  234. * For example if the application was
  235. * mounted as "/admin", which itself
  236. * was mounted as "/blog" then the
  237. * return value would be "/blog/admin".
  238. *
  239. * @return {String}
  240. * @api private
  241. */
  242. app.path = function(){
  243. return this.parent
  244. ? this.parent.path() + this.route
  245. : '';
  246. };
  247. /**
  248. * Check if `setting` is enabled (truthy).
  249. *
  250. * app.enabled('foo')
  251. * // => false
  252. *
  253. * app.enable('foo')
  254. * app.enabled('foo')
  255. * // => true
  256. *
  257. * @param {String} setting
  258. * @return {Boolean}
  259. * @api public
  260. */
  261. app.enabled = function(setting){
  262. return !!this.set(setting);
  263. };
  264. /**
  265. * Check if `setting` is disabled.
  266. *
  267. * app.disabled('foo')
  268. * // => true
  269. *
  270. * app.enable('foo')
  271. * app.disabled('foo')
  272. * // => false
  273. *
  274. * @param {String} setting
  275. * @return {Boolean}
  276. * @api public
  277. */
  278. app.disabled = function(setting){
  279. return !this.set(setting);
  280. };
  281. /**
  282. * Enable `setting`.
  283. *
  284. * @param {String} setting
  285. * @return {app} for chaining
  286. * @api public
  287. */
  288. app.enable = function(setting){
  289. return this.set(setting, true);
  290. };
  291. /**
  292. * Disable `setting`.
  293. *
  294. * @param {String} setting
  295. * @return {app} for chaining
  296. * @api public
  297. */
  298. app.disable = function(setting){
  299. return this.set(setting, false);
  300. };
  301. /**
  302. * Configure callback for zero or more envs,
  303. * when no `env` is specified that callback will
  304. * be invoked for all environments. Any combination
  305. * can be used multiple times, in any order desired.
  306. *
  307. * Examples:
  308. *
  309. * app.configure(function(){
  310. * // executed for all envs
  311. * });
  312. *
  313. * app.configure('stage', function(){
  314. * // executed staging env
  315. * });
  316. *
  317. * app.configure('stage', 'production', function(){
  318. * // executed for stage and production
  319. * });
  320. *
  321. * Note:
  322. *
  323. * These callbacks are invoked immediately, and
  324. * are effectively sugar for the following:
  325. *
  326. * var env = process.env.NODE_ENV || 'development';
  327. *
  328. * switch (env) {
  329. * case 'development':
  330. * ...
  331. * break;
  332. * case 'stage':
  333. * ...
  334. * break;
  335. * case 'production':
  336. * ...
  337. * break;
  338. * }
  339. *
  340. * @param {String} env...
  341. * @param {Function} fn
  342. * @return {app} for chaining
  343. * @api public
  344. */
  345. app.configure = function(env, fn){
  346. var envs = 'all'
  347. , args = [].slice.call(arguments);
  348. fn = args.pop();
  349. if (args.length) envs = args;
  350. if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
  351. return this;
  352. };
  353. /**
  354. * Delegate `.VERB(...)` calls to `router.VERB(...)`.
  355. */
  356. methods.forEach(function(method){
  357. app[method] = function(path){
  358. if ('get' == method && 1 == arguments.length) return this.set(path);
  359. // deprecated
  360. if (Array.isArray(path)) {
  361. console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0');
  362. }
  363. // if no router attached yet, attach the router
  364. if (!this._usedRouter) this.use(this.router);
  365. // setup route
  366. this._router[method].apply(this._router, arguments);
  367. return this;
  368. };
  369. });
  370. /**
  371. * Special-cased "all" method, applying the given route `path`,
  372. * middleware, and callback to _every_ HTTP method.
  373. *
  374. * @param {String} path
  375. * @param {Function} ...
  376. * @return {app} for chaining
  377. * @api public
  378. */
  379. app.all = function(path){
  380. var args = arguments;
  381. methods.forEach(function(method){
  382. app[method].apply(this, args);
  383. }, this);
  384. return this;
  385. };
  386. // del -> delete alias
  387. app.del = app.delete;
  388. /**
  389. * Render the given view `name` name with `options`
  390. * and a callback accepting an error and the
  391. * rendered template string.
  392. *
  393. * Example:
  394. *
  395. * app.render('email', { name: 'Tobi' }, function(err, html){
  396. * // ...
  397. * })
  398. *
  399. * @param {String} name
  400. * @param {String|Function} options or fn
  401. * @param {Function} fn
  402. * @api public
  403. */
  404. app.render = function(name, options, fn){
  405. var opts = {}
  406. , cache = this.cache
  407. , engines = this.engines
  408. , view;
  409. // support callback function as second arg
  410. if ('function' == typeof options) {
  411. fn = options, options = {};
  412. }
  413. // merge app.locals
  414. utils.merge(opts, this.locals);
  415. // merge options._locals
  416. if (options._locals) utils.merge(opts, options._locals);
  417. // merge options
  418. utils.merge(opts, options);
  419. // set .cache unless explicitly provided
  420. opts.cache = null == opts.cache
  421. ? this.enabled('view cache')
  422. : opts.cache;
  423. // primed cache
  424. if (opts.cache) view = cache[name];
  425. // view
  426. if (!view) {
  427. view = new (this.get('view'))(name, {
  428. defaultEngine: this.get('view engine'),
  429. root: this.get('views'),
  430. engines: engines
  431. });
  432. if (!view.path) {
  433. var err = new Error('Failed to lookup view "' + name + '"');
  434. err.view = view;
  435. return fn(err);
  436. }
  437. // prime the cache
  438. if (opts.cache) cache[name] = view;
  439. }
  440. // render
  441. try {
  442. view.render(opts, fn);
  443. } catch (err) {
  444. fn(err);
  445. }
  446. };
  447. /**
  448. * Listen for connections.
  449. *
  450. * A node `http.Server` is returned, with this
  451. * application (which is a `Function`) as its
  452. * callback. If you wish to create both an HTTP
  453. * and HTTPS server you may do so with the "http"
  454. * and "https" modules as shown here:
  455. *
  456. * var http = require('http')
  457. * , https = require('https')
  458. * , express = require('express')
  459. * , app = express();
  460. *
  461. * http.createServer(app).listen(80);
  462. * https.createServer({ ... }, app).listen(443);
  463. *
  464. * @return {http.Server}
  465. * @api public
  466. */
  467. app.listen = function(){
  468. var server = http.createServer(this);
  469. return server.listen.apply(server, arguments);
  470. };