三步法解析 Express 源码
关注公众号“执鸢者”,获取大量教学视频及私人总结面筋并进入专业交流群.
在抖音上有幸看到一个程序员讲述如何阅读源代码,主要分为三步:领悟思想、把握设计、体会细节。
1. 领悟思想:只需体会作者设计框架的初衷和目的
2. 把握设计:只需体会代码的接口和抽象类以及宏观的设计
3. 体会细节:是基于顶层的抽象接口设计,逐渐展开代码的画卷
基于上述三步法,迫不及待的拿Express开刀了。本次源码解析有什么不到位的地方各位读者可以在下面留言,我们一起交流。
一、领悟思想
在Express中文网上,介绍Express是基于Node.js平台,快速、开放、极简的Web开发框架。在这句话里面可以得到解读出以下几点含义:
1. Express是基于Node.js平台,并且具备快速、极简的特点,说明其初衷就是为了通过扩展Node的功能来提高开发效率。
2. 开放的特点说明该框架不会对开发者过多的限制,可以自由的发挥想象进行功能的扩展。
3. Express是Web开发框架,说明作者的定位就是为了更加方便的帮助我们处理HTTP的请求和响应。
二、把握设计
理解了作者设计的思想,下面从源码目录、核心设计原理及抽象接口三个层面来对Express进行整体的把握。
2.1 源码目录
如下所示是Express的源码目录,相比较来说还是比较简单的。
├─application.js---创建Express应用后可直接调用的api均在此处(核心)<br/>
├─express.js---入口文件,创建一个Express应用<br/>
├─request.js---丰富了http中request实例上的功能<br/>
├─response.js---丰富了http中response实例上的功能<br/>
├─utils.js---工具函数<br/>
├─view.js---与模板渲染相关的内容<br/>
├─router---与路由相关的内容(核心)<br/>
| ├─index.js<br/>
| ├─layer.js<br/>
| └route.js<br/>
├─middleware---与中间件相关的内容<br/>
| ├─init.js---会将新增加在request和response新增加的功能挂载到原始请求的request和response的原型上<br/>
| └query.js---将请求url中的query部分添加到request的query属性上<br/>
2.2 抽象接口
对源码的目录结构有了一定了解,下面利用UML类图对该系统各个模块的依赖关系进一步了解,为后续源码分析打好基础。
2.3 设计原理
这一部分是整个Express框架的核心,下图是整个框架的运行流程,一看是不是很懵逼,为了搞清楚这一部分,需要明确四个概念:Application、Router、Layer、Route。
为了明确上述四个概念,先引入一段代码
Application
表示一个Express应用,通过express()即可进行创建。
Router<
路由系统,用于调度整个系统的运行,在上述代码中该路由系统包含app.get('/test1',……)和app.get('/test2',……)两大部分
Layer
代表一层,对于上述代码中app.get('/test1',……)和app.get('/test2',……)都可以成为一个Layer
Route
一个Layer中会有多个处理函数的情况,这多个处理函数构成了Route,而Route中的每一个函数又成为Route中的Layer。对于上述代码中,app.get('/test1',……)中的两个函数构成一个Route,每个函数又是Route中的Layer。
了解完上述概念后,结合该幅图,就大概能对整个流程有了直观感受。首先启动服务,然后客户端发起了http://localhost:3000/test2的请求,该过程应该如何运行呢?
1. 启动服务时会依次执行程序,将该路由系统中的路径、请求方法、处理函数进行存储(这些信息根据一定结构存储在Router、Layer和Route中)
2. 对相应的地址进行监听,等待请求到达。
3. 请求到达,首先根据请求的path去从上到下进行匹配,路径匹配正确则进入该Layer,否则跳出该Layer。
4. 若匹配到该Layer,则进行请求方式的匹配,若匹配方式匹配正确,则执行该对应Route中的函数。
上述解释的比较简单,后续会在细节部分进一步阐述。
三、体会细节
通过上述对Express设计原理的分析,下面将从两个方面做进一步的源码解读,下面流程图是一个常见的Express项目的过程,首先会进行app实例初始化、然后调用一系列中间件,最后建立监听。对于整个工程的运行来说,主要分为两个阶段:初始化阶段、请求处理阶段,下面将以app.get()为例来阐述一下该核心细节。
3.1 初始化阶段
下面利用app.get()这个路由来了解一下工程的初始化阶段。
首先来看一下app.get()的内容(源代码中app.get()是通过遍历methods的方式产生)
在app.lazyrouter()会完成router的实例化过程
注意:该过程中其实是利用了单例模式,保证整个过程中获取router实例的唯一性。
调用router.route()方法完成layer的实例化、处理及保存,并返回实例化后的route。(注意源码中是proto.route)
将该app.get()中的函数存储到route的stack中。(注意源码中也是通过遍历method的方式将get挂载到route的prototype上)
注意:上述代码均删除了源码中一些异常判断逻辑,方便读者看清整体框架。
通过上述的分析,可以看出初始化阶段主要做了两件事情:
>
1. 将路由处理方式(app.get()、app.post()……)、app.use()等划分为路由系统中的一个Layer。
2. 对于每一个层中的处理函数全部存储至Route对象中,一个Route对象与一个Layer相互映射。
3.2 请求处理阶段
当服务启动后即进入监听状态,等待请求到达后进行处理。
app.listen()使服务进入监听状态(实质上是调用了http模块)
当连接建立会调用app实例,app实例中会立即执行app.handle()函数,app.handle()函数会立即调用路由系统的处理函数router.handle()
router.handle()主要是根据路径获取是否有匹配的layer,当匹配到之后则调用layer.prototype.handle_request()去执行route中内容的处理
layer.handle_request()会调用route.dispatch()触发route中内容的执行
route中的通过判断请求的方法和route中layer的方法是否匹配,匹配的话则执行相应函数,若所有route中的layer都不匹配,则调到外层的layer中继续执行。
通过上述的分析,可以看出初始化阶段主要做了两件事情:
1. 首先判断layer中的path和请求的path是否一致,一致则会进入route进行处理,否则调到下一层layer
2. 在route中会判断route中的layer与请求方法是否一致,一致的话则函数执行,否则不执行,所有route中的layer执行完后跳到下层的layer进行执行。
1.如果觉得这篇文章还不错,来个分享、点赞、吧,让更多的人也看到
2.关注公众号执鸢者,领取学习资料,定期为你推送原创深度好文
欢迎大家关注公众号(回复“书籍”获取大量前端学习资料,回复“前端视频”获取大量前端教学视频)
版权声明: 本文为 InfoQ 作者【执鸢者】的原创文章。
原文链接:【http://xie.infoq.cn/article/1057246b3faf2ce44c5c5b556】。文章转载请联系作者。
评论