Projekt

Obecné

Profil

Stáhnout (13.9 KB) Statistiky
| Větev: | Revize:
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
}
(1-1/6)