Node 中间件探索:用研 express 4.x
目录
用
在日常生活中,面对一件新物品,你会怎么开始使用它呢?
比方说它是数码相机(Digital Camera):一般人可能会凭借着以往使用智能手机拍照的经验,通过“试错”的方式去开始使用它。
但如果是数码单反相机(Digital Single Lens Reflex)呢?可能以往使用智能手机和数码相机的经验都会不够用,而且试错的成本较大。这时候我们会通过阅读产品说明书的方式去学习。
对于我而言,express 就是那个数码单反相机。如何使用它?我是从《开始使用》和《指南》开始的。
Examples
阅读完产品说明书,你已经蠢蠢欲动了。但是它到底能怎么拍,拍出什么,你可能需要一些示范。学习的初级阶段,最保险的方法就是“模仿”。翻一翻数码单反官网的素材,上摄影社区看看大师的作品:它们使你了解参数设置的意义、掌握常用的拍摄技巧……
对于 express 而言,它本身提供了大量的示例供使用者学习,这些示例可以从源码的 examples目录中找到。
examples 目录下的文件夹名表示了它的用途,里面代码注释都足够详细,使用者可以通过阅读相应的源码初步掌握 express 的使用技巧和常用 API 。
手写代码
你已经发现了。examples 下的代码实在是 too young too simple 了。
实际的线上应用会存在复杂的业务逻辑、多人协作的开发环境、对性能苛刻的要求……
在实际的产品中,还需要很多非编码相关的工作以保证项目的进展和产品的正常运行,这些细节包括项目工程化、部署运维、性能、日志、监控、稳定性……
还等什么,把示例组合起来,用 express 手写一个简单的cms?
研
你只要掌握了数码单反的使用方法和常用技巧,就能拍出好看的照片了。可是,强烈的热爱还是会使你忍不住疑问:按下快门的那一下,光线通过镜头到达传感器最后形成图像,中间到底发生了什么?
带着类似的疑问,我开始去读 express 的源码。
先给大家贴个最简单的示例,以下是贯穿全章节的代码:
```
var express = require('express');
var app = express();
app.use(function(req, res, next){
console.log('I am a middleware');
next();
});
app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
```
初始化流程
我把上面的这些代码称之为“初始化流程”,它们初始化了整个服务端应用。
然后我们来看一看,每一个函数背后到底做了些什么。为了保持文章的简洁,我不会贴出函数的源码,但是每个函数都会有对应的链接,点击链接即可看到源码。
express()
创建一个 express 应用:
```
var express = require('express');
var app = express();
```
它将运行 /lib/express.js 内的 createApplication 方法,方法内部:
- 创建 app 函数对象
- “继承” /lib/application.js
- “继承” events.EventEmitter (事件能力)
- 声明 request(包装过的请求对象)、response(包装过的响应对象) 属性
- 运行 /lib/application.js 内 app.init 方法
app.init 方法内部:
- 初始化 app 对象的私有属性
- 运行 app.defaultConfiguration 方法
app.defaultConfiguration 方法内部:
- 初始化 app.settings
- 初始化 app.locals
- 初始化 app.mountpath = /
app.use()
注册一个中间件:
```
app.use(function(req, res, next){
console.log('I am a middleware');
next();
});
```
运行 /lib/application.js 内的 app.use 方法,方法内部:
- app.lazyrouter:初始化 app._router 属性
- 实例化 /lib/router/index.js 做中间件管理器:
this._router = new Router(options)
- 运行 /lib/middleware/query.js 方法:该方法返回了 query 中间件,该中间件的作用是声明 req.query 属性
- 运行 /lib/middleware/init.js 内的 init 方法:该方法返回了 expressInit 中间件,该中间件的作用是初始化 app.request 和 app.response 对象到局部变量 req 和 res 原型链中供其他中间件使用。
- 运行 this._router.use 方法
- 实例化 /lib/router/index.js 做中间件管理器:
- app._router.use:注册中间件
- 通过实例化 /lib/router/layer.js 包装中间件:
var layer = new Layer(path, options, middlewareFn)
- 将 layer 存入 app._router.stack 队列:
this.stack.push(layer)
- 通过实例化 /lib/router/layer.js 包装中间件:
app.get()
注册一个路由:
```
app.get('/', function(req, res){
res.send('Hello World');
});
```
/lib/application.js 内的 app.get 方法,方法内部:
- lazyroter (同上)
- 运行 app._route.route 方法:
- 实例化 /lib/router/route.js 做路由中间件:
var route = new Route()
- 实例化 /lib/router/layer.js 包装路由中间件
var layer = new Layer(path, options, router.dispatch.bind(route))
- 将 layer 存入 app._router.stack 队列:
this.stack.push(layer)
- 实例化 /lib/router/route.js 做路由中间件:
- 运行 /lib/router/route.js 内的 get 方法:为路由中间件注册响应函数:
router.get(fn)
- 通过运行 /lib/router/layer.js 包装路由响应函数:
var layer = Layer('/', {}, fn)
- 将 layer 存入 route.stack 队列:
this.stack.push(layer)
- 通过运行 /lib/router/layer.js 包装路由响应函数:
app.listen()
开启服务端口监听:
app.listen(3000);
listen 的实现非常简洁,源码只有2行:
```
app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
```
就是创建一个 http 服务并调用 http.listen 方法。其中需要注意到的是将 this 作为参数传给了 createServer。而这里的 this 是什么呢?就是 /lib/express.js 内的 createApplication 方法内声明的 app 函数:
```
var app = function(req, res, next) {
app.handle(req, res, next);
};
```
请求处理流程
我把一个客户端请求 127.0.0.1:3000 时,express 应用是如何找到对应的路由函数并响应的过程称之为“请求处理流程”。
app.handle
正如上一节所描述的,请求会先来到 app.handle 方法。
该方法主要做了三件事情:
- 声明 http 尾应答函数:finalhandler
- 判断中间件和路由的注册情况
- 调用 router.handle
app._router.handle
app._router.handle 的所做的事情就是在 app._router.stack 中逐一取出 layer 并执行 handle_request
-
尾触发函数:在 app._router.stack 中取出匹配 path 的 layer
-
处理通过 app.param 注册的函数
-
针对匹配了路径的中间件重设 req.url 和 req.baseUrl
-
运行 layer.handle_request
layer.handle_request
layer.handle_request 就是取出注册函数并执行,传入 request 对象、response 对象、尾触发函数 next 作为参数。
背后设计
我们来看看 Express 各模块职责:
```html
---/index.js 包入口文件:加载并暴露了 /lib/express.js
---/lib/application.js 定义了 Express 应用
---/lib/express.js 包对外提供服务的总接口
---/lib/request.js http 请求对象的增强
---/lib/response.js http 响应对象的增强
---/lib/utils.js 通用工具模块
---/lib/view.js 渲染处理模块
---/lib/middleware
------/lib/init.js 初始化的中间件
------/lib/query.js query 中间件
---/lib/router
------/lib/index.js 中间件管理器
------/lib/layer.js 中间件包装器
------/lib/route.js 路由响应函数包装器
```
以及他们相互之间的关系: