Koa源码

Koa

Compose

简介

Koa中间件机制就是组合函数(Compose)的概念, 将⼀组需要顺序执行的函数复合为⼀个函数,外层函数的参数实际是内层函数的 返回值 。洋葱圈模型可以形象表示这种机制,这也是Koa的 核心

如下图,假设我们注册中间件的顺序是A,B,C,那么中间件的执行顺序是,ABCBA。

那么外层函数的参数是内层函数的返回值该怎么理解呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const app = new Koa();
app.use(async (ctx, next) => {
console.log('A')
await next();
const router = {};
router["/html"] = (ctx) => {
ctx.type = "text/html;charset=utf-8";
ctx.body = `<b>my name is:${ctx.body[0].name}</b>`;
};
const fun = router[ctx.url];
fun && fun(ctx);
});
app.use(async (ctx, next) => {
console.log('A')
ctx.body = [
{
name: ctx.url,
},
];
});
app.listen(3000, (res) => {
console.log("listening");
});

上述代码中,在B中间件给ctx的body赋值为一个数组,在A组件中就能通过ctx参数拿到这个数组,进行一些操作。

原理

其实配合es7的async / await 可以很方便的实现Compose。当然,Compose只是Koa重要部分,还有一些细节也一并实现。

可以看到下面代码引入了三个包 context,request,response ,其实就是对http做了一些简单的封装。

context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
get url() {
return this.request.url;
},
get method() {
return this.request.method;
},
get body() {
return this.response.body;
},
set body(val) {
this.response.body = val;
},
};

request

1
2
3
4
5
6
7
8
module.exports = {
get url() {
return this.req.url;
},
get method() {
return this.req.method.toLowerCase();
},
};

response

1
2
3
4
5
6
7
8
module.exports = {
get body() {
return this._body;
},
set body(val) {
this._body = val;
},
};

代码

Compose的实现其实跟express差不多,只不过用Promise封装了一下,但是因为async,await的存在,让它和express形成了不同的执行顺序。

除此之外我们定义了一个ctx(Context)对象,对http的req,res进行了简单的封装然后进行传递。

1
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
const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
class Application {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
createContext(req, res) {
const ctx = Object.create(context);
ctx.request = Object.create(request);
ctx.response = Object.create(response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
compose(middlewares) {
return function (ctx) {
return dispatch(0);
function dispatch(idx) {
const fn = middlewares[idx];
if (!fn) {
return Promise.resolve();
}
return Promise.resolve(
fn(ctx, function next() {
return dispatch(idx + 1);
})
);
}
};
}
listen(...args) {
const server = http.createServer(async (req, res) => {
const ctx = this.createContext(req, res);
const fn = this.compose(this.middlewares);
await fn(ctx);
res.end(ctx.body);
});
server.listen(...args);
}
}

module.exports = Application;

路由机制

Koa单独将路由拆分出来,通过use注册路由中间件。

1
2
3
const router = new Router();
// 注册路由中间件
app.use(router.routes());

Koa的路由相对于Express来说要简单很多,只是实现了基本的功能,因为它不需要支持多个handle的调用。

1
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
class Router {
constructor() {
this.stack = [];
}
register(path, method, middleware) {
let route = { path, method, middleware };
this.stack.push(route);
}
get(path, middleware) {
this.register(path, "get", middleware);
}
post(path, middleware) {
this.register(path, "post", middleware);
}
routes() {
const stack = this.stack;
return async function (ctx, next) {
let currUrl = ctx.url;
let route;
for (let i = 0; i < stack.length; i++) {
const item = stack[i];
if (item.path === currUrl && item.method === ctx.method) {
route = item.middleware;
break;
}
}
if (typeof route === "function") {
route(ctx, next);
return;
}
await next();
};
}
}

module.exports = Router;

Koa源码
https://jing-jiu.github.io/jing-jiu/2022/07/25/Node/Koa/
作者
Jing-Jiu
发布于
2022年7月25日
许可协议