connect实现原理

connect是一款node中间件框架。koa,express等主流node框架都是基于connect开发的。connect本身非常精简,是一个比较基础的框架。

从原理上讲connect其实也非常简单,就是将一系列的组件链接在一起,然后http请求依次经过这些组件。这些组件就是中间件,每个中间件独立处理各自的任务。比较常用的中间有bodyparse,session。组件之间通过req和res对象传递数据。

给个github地址https://github.com/senchalabs/connect
从源码上来看算上注释才200多行。

git上的例子

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
var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
keys: ['secret1', 'secret2']
}));

// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded());

// respond to all requests
app.use(function(req, res){
res.end('Hello from Connect!\n');
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

从例子就能看出,connect用use保存中间件。然后当请求来到的时候,挨个触发这些中间件。
看看use的实现,就是把route和对应的handle,存放到stack中。

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
var handle = fn;
var path = route;

// default route to '/'
if (typeof route !== 'string') {
handle = route;
path = '/';
}

// wrap sub-apps
if (typeof handle.handle === 'function') {
var server = handle;
server.route = path;
handle = function (req, res, next) {
server.handle(req, res, next);
};
}

// wrap vanilla http.Servers
if (handle instanceof http.Server) {
handle = handle.listeners('request')[0];
}

// strip trailing slash
if (path[path.length - 1] === '/') {
path = path.slice(0, -1);
}

// add the middleware
debug('use %s %s', path || '/', handle.name || 'anonymous');
this.stack.push({ route: path, handle: handle });

return this;

然后看看handle中核心代码的实现,当有请求的时候,通过请求的path找到对应的route并且执相应的handle。

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
proto.handle = function handle(req, res, out) {
var index = 0;
var protohost = getProtohost(req.url) || '';
var removed = '';
var slashAdded = false;
var stack = this.stack;

// final function handler
var done = out || finalhandler(req, res, {
env: env,
onerror: logerror
});

// store the original URL
req.originalUrl = req.originalUrl || req.url;

function next(err) {
xxxx
}

// call the layer handle
call(layer.handle, route, err, req, res, next);
}

next();
};

抄一段别人的总结,感觉说得非常到位
总的来看,connect其实是典型的chain of responsibility模式的实现。各个中间件就是责任链上的一个节点,route pattern就是决定中间件是否参与请求处理的判断条件。链只知道请求的传递,而中间件只知道自己的处理逻辑,这样对象之间的耦合是非常松散的,任务的指派过程也是相当灵活的。第三方按照中间件的接口约定即可开发自己的中间件并加入到connect中,同时还可以调整链的构建顺序来控制请求处理的流程。但由于http处理流程的一些特殊性,有的中间件之间也存在有一些隐含的依赖关系(如前面提到的cookie和session),所以connect对中间件的加载顺序也有着一定的要求。