comparison node_modules/express/lib/application.js @ 73:0c3a2942ddee

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