- 请求到达,首先根据请求的path去从上到下进行匹配,路径匹配正确则进入该Layer,否则跳出该Layer。
- 若匹配到该Layer,则进行请求方式的匹配,若匹配方式匹配正确,则执行该对应Route中的函数。
上述解释的比较简单,后续会在细节部分进一步阐述。
三、体会细节
通过上述对Express设计原理的分析,下面将从两个方面做进一步的源码解读,下面流程图是一个常见的Express项目的过程,首先会进行app实例初始化、然后调用一系列中间件,最后建立监听。对于整个工程的运行来说,主要分为两个阶段:初始化阶段、请求处理阶段,下面将以app.get()为例来阐述一下该核心细节。
3.1 初始化阶段
下面利用app.get()这个路由来了解一下工程的初始化阶段。
- 首先来看一下app.get()的内容(源代码中app.get()是通过遍历methods的方式产生)
app.get = function(path){
// ……
this.lazyrouter();
var route = this._router.route(path);
route.get.apply(route, slice.call(arguments, 1));
return this;
};
- 在app.lazyrouter()会完成router的实例化过程
app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled(‘case sensitive routing’),
strict: this.enabled(‘strict routing’)
});
// 此处会使用一些中间件
this._router.use(query(this.get(‘query parser fn’)));
this._router.use(middleware.init(this));
}
};
注意:该过程中其实是利用了单例模式,保证整个过程中获取router实例的唯一性。
- 调用router.route()方法完成layer的实例化、处理及保存,并返回实例化后的route。(注意源码中是proto.route)
router.prototype.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放到layer上
this.stack.push(layer); // 把layer放到数组中
return route;
};
- 将该app.get()中的函数存储到route的stack中。(注意源码中也是通过遍历method的方式将get挂载到route的prototype上)
Route.prototype.get = function(){
var handles = flatten(slice.call(arguments));
for (var i = 0; i < handles.length; i++) {
var handle = handles[i];
// ……
// 给route添加layer,这个层中需要存放方法名和handler
var layer = Layer(‘/’, {}, handle);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
}
return this;
};
注意:上述代码均删除了源码中一些异常判断逻辑,方便读者看清整体框架。
通过上述的分析,可以看出初始化阶段主要做了两件事情:
- 将路由处理方式(app.get()、app.post()……)、app.use()等划分为路由系统中的一个Layer。
- 对于每一个层中的处理函数全部存储至Route对象中,一个Route对象与一个Layer相互映射。
3.2 请求处理阶段
当服务启动后即进入监听状态,等待请求到达后进行处理。
- app.listen()使服务进入监听状态(实质上是调用了http模块)
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
- 当连接建立会调用app实例,app实例中会立即执行app.handle()函数,app.handle()函数会立即调用路由系统的处理函数router.handle()
app.handle = function handle(req, res, callback) {
var router = this._router;
// 如果路由系统中处理不了这个请求,就调用done方法
var done = callback || finalhandler(req, res, {
env: this.get(‘env’),
onerror: logerror.bind(this)
});
//……
router.handle(req, res, done);
};
- router.handle()主要是根据路径获取是否有匹配的layer,当匹配到之后则调用layer.prototype.handle_request()去执行route中内容的处理
router.prototype.handle = function handle(req, res, out) {
// 这个地方参数out就是done,当所有都匹配不到,就从路由系统中出来,名字很形象
var self = this;
// ……
var stack = self.stack;
// ……
next();
function next(err) {
// ……
// get pathname of request
var path = getPathname(req);
// 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;
// ……
}
// no match
if (match !== true) {
return done(layerError);
}
// ……
// Capture one-time layer values
req.params = self.mergeParams
-
? mergeParams(layer.params, parentParams)
- layer.params;
var layerPath = layer.path;
// 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);
});
}
function trim_prefix(layer, layerError, layerPath, path) {
// ……
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
- layer.handle_request()会调用route.dispatch()触发route中内容的执行
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
- route中的通过判断请求的方法和route中layer的方法是否匹配,匹配的话则执行相应函数,若所有route中的layer都不匹配,则调到外层的layer中继续执行。
Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
// ……
next();
// 此next方法是用户调用的next,如果调用next会执行内层的next方法,如果没有匹配到会调用外层的next方法
function next(err) {
// ……
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next(err);
}
// 如果当前route中的layer的方法匹配到了,执行此layer上的handler
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。有需要面试题资料的朋友点击这里即可获取!!!。
存中…(img-P2vRtnZP-1711660546264)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。有需要面试题资料的朋友点击这里即可获取!!!。
[外链图片转存中…(img-FiRryVZs-1711660546265)]