Null's Blog

Express(4+)框架浅析1 --[app对象]

犹豫了好久,最终决定把express(4.13.3)写成一个系列,这样或许可以多拿点kpi。本人假设读者有一定nodejs基础并了解express的基本使用。无论如何,这里都先放上一个广告,本人博客,为何在群里宣传了这么久,star却并没有增加-_-。

1.理解app

在使用Express的时候,通常会以如下方式创建一个应用:

1
2
3
var express require('express');
var app = express();
app.listen(3000);

在不使用express的情况下,是这样做的:

1
2
3
4
5
var http = require('http');
http.createServer(function(req,res){
res.write('Hello world!');
res.end
}).listen(3000);

通过源码,不难发现express其实是一个工厂函数,用来创建app:(express\lib\express.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
app.init();
return app;
}

express\lib\application.js:

1
2
3
4
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

因此,在使用express的时候,所有的请求都交给app.handle进行处理了,虽然router也有自己的处理逻辑,但也是可以看作app的一个小型示例,会有自己root节点,所以这里插播一条将来会讲到的router部分的内容:

1
2
3
4
5
6
//匹配 localhost/
app.get('/',function(req,req){});
//匹配localhost/list/
app.use('/list', router);
router.get('/',function(req,res){});

2.模块依赖

虽然本人觉得这部分并不重要,但一开始即讲app内的方法,似乎有点突然,所以这里仅贴上源码的内容凑紫薯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var finalhandler = require('finalhandler');
var Router = require('./router');
var methods = require('methods');
var middleware = require('./middleware/init');
var query = require('./middleware/query');
var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var deprecate = require('depd')('express');
var flatten = require('array-flatten');
var merge = require('utils-merge');
var resolve = require('path').resolve;
var slice = Array.prototype.slice;

3.app初始化

1
var app = exports = module.exports = {};

导出一个对象字面量,这里没什么好说的,init方法会扩展这个对象:

1
2
3
4
5
6
app.init = function init() {
this.cache = {};
this.engines = {};
this.settings = {};
this.defaultConfiguration();
};

通过源码可知,初始化cache,engines(模版引擎),settings之后,调用默认配置函数进行默认配置。

4.app.listen

listen方法很简单,使用apply函数调用http模块:

1
2
3
4
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

##5.setting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app.set = function set(setting, val) {
if (arguments.length === 1) {
// app.get(setting)
return this.settings[setting];
}
debug('set "%s" to %o', setting, val);
// set value
this.settings[setting] = val;
// trigger matched settings
switch (setting) {
case 'etag':
this.set('etag fn', compileETag(val));
break;
case 'query parser':
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy':
this.set('trust proxy fn', compileTrust(val));
// trust proxy inherit back-compat
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
configurable: true,
value: false
});
break;
}
return this;
};

很好理解,根据

parser```,```trust proxy```时,会进行额外的设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 6.默认配置 defaultConfiguration
刚才说过,在执行init的时候会进行默认配置```defaultConfiguration```:
```javascript
app.defaultConfiguration = function defaultConfiguration() {
var env = process.env.NODE_ENV || 'development';
// default settings
this.enable('x-powered-by');
this.set('etag', 'weak');
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
// trust proxy inherit back-compat
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
configurable: true,
value: true
});
debug('booting in %s mode', env);
this.on('mount', function onmount(parent) {
// inherit trust proxy
if (this.settings[trustProxyDefaultSymbol] === true
&& typeof parent.settings['trust proxy fn'] === 'function') {
delete this.settings['trust proxy'];
delete this.settings['trust proxy fn'];
}
// inherit protos
this.request.__proto__ = parent.request;
this.response.__proto__ = parent.response;
this.engines.__proto__ = parent.engines;
this.settings.__proto__ = parent.settings;
});
// setup locals
this.locals = Object.create(null);
// top-most app is mounted at /
this.mountpath = '/';
// default locals
this.locals.settings = this.settings;
// default configuration
this.set('view', View);
this.set('views', resolve('views'));
this.set('jsonp callback name', 'callback');
if (env === 'production') {
this.enable('view cache');
}
Object.defineProperty(this, 'router', {
get: function() {
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.');
}
});
};

这里做了几件事:
1.设置x-powered-byetagenvquery parsersubdomain offset
2.注册mount事件回调。
3.增加this.locals(用于保存全局环境变量)this.mountpath(保存当前挂载路径)
至此,express框架app的主要方法已经介绍完毕,其实express框架并不复杂,希望各位了解其原理之后可以在开发少跳坑,少挖坑,并且能在需要时快速开发出牛逼的插件。

7.下期预告:Express(4+)框架浅析2 –[router] 敬请期待