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