Node 中间件探索:用研 express 4.x

Node.js

目录

在日常生活中,面对一件新物品,你会怎么开始使用它呢?

比方说它是数码相机(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.init 方法内部:

app.defaultConfiguration 方法内部:

app.use()

注册一个中间件:

```
app.use(function(req, res, next){
    console.log('I am a middleware');
    next();
});
```

运行 /lib/application.js 内的 app.use 方法,方法内部:

app.get()

注册一个路由:

```
app.get('/', function(req, res){
    res.send('Hello World');
});
```

/lib/application.js 内的 app.get 方法,方法内部:

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 方法。

该方法主要做了三件事情:

app._router.handle

app._router.handle 的所做的事情就是在 app._router.stack 中逐一取出 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         路由响应函数包装器
```

以及他们相互之间的关系:

职责图