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