express源码解读
SunShinewyf opened this issue · 1 comments
最近在研究experss
,在造过一个简单的轮子之后,想通过研究一下它的源码来了解一下内部的实现原理,如有不对的地方希望得到大家的指正。
研究的express
版本为4.15.0
,应该算是比较新的版本了。
文件结构及内容
- lib/
- middleware/
- init.js
- query.js
- router/
- index.js
- layer.js
- route.js
- application.js
- express.js
- request.js
- response.js
- utils.js
- view.js
- index.js
上图中的目录结构比较清晰,router
目录中主要是router
路由的功能,middleware
目录是中间件的一些功能。比较重要的是express.js
和application.js
还有router
文件夹中的文件。express.js
中很简单,只是暴露了一个工厂函数createApplication
,这个函数虽然简洁,但是却完成了生成一个完成app
的整个过程,具体代码如下:
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
...
return app;
}
在application.js
中,可以看到app.listen
的实现:
app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
由此就可以知道使用express
实例化一个项目,其实和使用原生node
创建一个应用的步骤差不多,只是express
进行了一层封装而已。
application.js
这个文件主要是暴露了express
内部的一些api
,比如app.render(),app.param(),app.set()
等。
路由
express
的router
中总共包含了三个文件,index.js
,route.js
和layer.js
,并且分别定义了router,route,layer
三个构造函数,其中router
和route
这两个对象都包含stack
这个成员属性,也就是中间件数组。并且router
和route
里面的stack
也不一样,如下图:
如上图可见,router
中的stack
数组是由route
对象组成的,而route
中的stack
则是由layer
数组组成的。三者关系可以描述成下图:
route
对象代表的是路由对象,每一条路由都会实例化一个route
对象,而router
则是一个路由集合,在上一篇中曾提到router
是一个“微型应用程序”,这说明router
和route
之间的关系是一个包含与被包含的关系,前者的功能更强大,后者只是处理单条路由的一些功能。
路由的整个功能和逻辑顺序大概如下:
在触发一个路由的时候,比如访问/login
时,会执行router
的成员函数handle()
,在这个函数中会将传进来的req
的url
做一些处理,比如获取url
里面的参数,并会遍历stack
数组中的每一个layer
,遍历主要是通过next()
实现的,这个next()
主要是非路由中间件的next()
函数,具体代码如下:
function next(err) {
....//省略了一些代码
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
// process non-route handlers normally
continue;
}
....//省略一些源码
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
.....
// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
trim_prefix(layer, layerError, layerPath, path);
});
}
遍历过程中会执行一个match()
,如果匹配成功(断点发现当stack.name=router
的时候会匹配成功),则会去执行route
中的dispatch
方法,则获取当前route
的stack
数组并通过next()
再进行遍历一次。当再次匹配成功之后,就会去调用layer
对象的handle_request()
方法。也就是调用中间件函数,这个函数的代码如下:
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
上面也是执行中间件的主要部分,首先是判断路由函数中传进来的回调函数的参数个数,若大于3,则属于路由中间件,则首先处理next()
,这里的next()
函数代码如下(定义在route.js
文件中:
function next(err) {
if (err && err === 'route') {
return done();
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next(err);
}
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
这个next()
主要是处理路由中间件的,不断遍历,使得中间件一个个执行。对于路由中间件和非路由中间件,两者也是有区别的,路由中间件的定义是在router/index.js
中的route
原型方法中,具体代码如下:
proto.route = function route(path) {
var route = new Route(path);
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route; //含有route属性
this.stack.push(layer);
return route;
};
从上面代码可以看出,路由中间件中的route
不为空值,且为一个route
对象,并且该对象中有methods
的一个成员属性,用来定义一些路由方法。再来看看非路由中间件的定义方法,放在了router/index.js
中的use
原型方法中,具体代码如下:
proto.use = function use(fn) {
var offset = 0;
var path = '/';
....//省略部分源码
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined; //其中的route属性为undefined
this.stack.push(layer);
}
return this;
};
从上面源码可以看出,非路由中间件中的route
属性值为undefined
,当使用app.use()
时其实也就是触发了router.use()
方法。
动态添加method
源码中并没有直接遍历路由中的每一种方法,比如get,post,put
,而是动态添加,不论是最后导出的app
示例,还是route
中对方法的处理,都是使用的这种方式,具体代码如下:
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
this.lazyrouter();
var route = this._router.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
在route
对象中,同样定义了这样一个处理method
的操作:
methods.forEach(function(method){
Route.prototype[method] = function(){
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
if (typeof handle !== 'function') {
var type = toString.call(handle);
var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
throw new Error(msg);
}
....
var layer = Layer('/', {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
});
这种方法比较灵活,不需要将所有的method
遍历一遍。
总结
express
的源码还是有点复杂的,新手初涉,可能有一些地方理解得不是很到位的,欢迎一起交流