浅记koa的洋葱模型实现
本篇相关github代码地址
github地址:https://github.com/niexq/koaComposeTest
1.简介
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
2.安装
Koa 依赖 node v7.6.0 或 ES2015及更高版本和 async 方法支持.
你可以使用自己喜欢的版本管理器快速安装支持的 node 版本:
1 | nvm install 7 |
3.中间件执行的洋葱模型
4.中间件级联
Koa 中间件以更传统的方式级联。对比 Connect 的实现,通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。
5.疑问点:
- 中间件如何加载
- 中间件执行顺序
- next是啥
- context如何传递
6.koa中间件执行代码
下面以 “Hello World” 的响应作为示例,当请求开始时首先请求流通过四个中间件,然后继续移交控制给 response 中间件。当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。
启动服务器代码index.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
require('colors');
const Koa = require('koa2');
const app = new Koa();
const firstMiddleware = async (ctx, next) => {
console.error('第一个中间件执行开始')
await next();
console.error('第一个中间件执行结束')
};
const secondMiddleware = async (ctx, next) => {
console.error('第二个中间件执行开始')
await next();
console.error('第二个中间件执行结束')
};
const thirdMiddleware = async (ctx, next) => {
console.error('第三个中间件执行开始')
await next();
console.error('第三个中间件执行结束')
};
const fourthMiddleware = async (ctx, next) => {
console.error('第四个中间件执行开始')
await next();
console.error('第四个中间件执行结束')
};
app.use(firstMiddleware)
app.use(secondMiddleware)
app.use(thirdMiddleware)
app.use(fourthMiddleware)
// response
app.use(async (ctx, next) => {
console.log('准备响应');
ctx.body = 'Hello World';
console.log('已响应');
});
console.error('koa2 server start: '.blue, 'http://localhost:3001'.green);
app.listen(3000);
启动服务node index.js,浏览器中访问http://localhost:3001/
访问后,在启动的服务命令窗口输出的结果:
1 | 第一个中间件执行开始 |
7.源码分析
app.listen 创建node的http服务
重点关注this.callback(),this.callback()生成node的http服务请求回调函数1
2
3
4
5listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback 返回node的http服务请求回调函数
1 | callback() { |
handleRequest 真正的请求回调函数
1 | handleRequest(ctx, fnMiddleware) { |
compose(this.middleware),此行代码处理中间件,继续跟踪app.use方法
app.use(function) 将给定的中间件方法添加到此应用程序
1 | use(fn) { |
app.use只负责将给定的中间件方法存入this.middleware数组中。
compose 中间件合成
1 | function compose (middleware) { |
compose方法中,递归调用dispatch函数,它将遍历整个middleware,然后将context和dispatch(i + 1)传给middleware中的方法, 这里的dispatch(i + 1)就是中间件方法的第二个入参next,通过next巧妙的把下一个中间件fn作为next的返回值。
8.疑问解答
至此就以上4点疑问就都可以解释了:
- 中间件如何加载(通过app.use方法存入this.middleware数组中,然后通过compose方法串联)
- 中间件执行顺序(dispatch(0),存在this.middleware数组里的中间件方法先进先执行,next()执行后转交下一中间件)
- next是啥(next是一个以下一个中间件为返回值的方法)
- context如何传递(context就在Promise.resolve(fn(context, dispatch.bind(null, i + 1)))一直传递)