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.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 方法
  • app._router.use:注册中间件
    • 通过实例化 /lib/router/layer.js 包装中间件:var layer = new Layer(path, options, middlewareFn)
    • 将 layer 存入 app._router.stack 队列:this.stack.push(layer)

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 内的 get 方法:为路由中间件注册响应函数:router.get(fn)
    • 通过运行 /lib/router/layer.js 包装路由响应函数:var layer = Layer('/', {}, fn)
    • 将 layer 存入 route.stack 队列:this.stack.push(layer)

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

  • next

    尾触发函数:在 app._router.stack 中取出匹配 path 的 layer

  • router.process_params

    处理通过 app.param 注册的函数

  • trim_prefix

    针对匹配了路径的中间件重设 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         路由响应函数包装器
```

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

职责图

Published: March 05 2015