# 手写 Koa
# 启动基本服务
建立 lib 文件夹用来存放手写的 koa 代码
写一个 app.js 用来测试我们的 koa
app.js
const YdKoa = require("./lib/application");
const app = new YdKoa();
app.use(async (ctx, next) => {
console.log("入口");
await next();
console.log("出口");
});
app.listen(3000, () => {
console.log("server started");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- 这是一个非常基本的启动服务的东西,包括插入中间件
接下来手写一下我们的 koa 的核心部分
application.js
const EventEmitter = require("events");
const http = require("http");
class Application extends EventEmitter {
constructor() {
super();
this.middlewares = [];
}
use(middleware) {
this.middlewares.push(middleware);
}
listen(...args) {
const server = http.createServer(this.callback());
server.listen(...args);
}
callback() {
return (req, res) => {
res.end("hello world");
};
}
}
module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 首先定义导出一个类
Application
- node 的所有事件都基于 events,所以我们的类要继承自
EventEmitter
- 定义一个
middlewares
属性用来存放我们的中间件 - 定义
use
方法,暂时把中间件存进去 - 定义 listen 方法启动服务,调用 node 的
http
模块 - 把
callback
方法到 server 中 - 在
callback
中能拿到 node 提供的res
和req
- 启动服务,打开网页可以看到 'hello world',一个简单的 koa 雏形就完成了
# 处理中间件
现在一个基本的服务已经启动了,但是我们的 middleware 还没有处理
const EventEmitter = require("events");
const http = require("http");
class Application extends EventEmitter {
constructor() {
super();
this.middlewares = [];
}
use(middleware) {
this.middlewares.push(middleware);
}
listen(...args) {
const server = http.createServer(this.callback());
server.listen(...args);
}
compose() {
// console.log(this.middlewares);
// 将所有的middlewares进行递归合并
return async ctx => {
function createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
};
}
let len = this.middlewares.length;
let next = async () => {
return Promise.resolve();
};
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
await next();
};
}
callback() {
return (req, res) => {
let fn = this.compose();
const ctx = {};
return fn(ctx).then(() => {
res.end("hello world");
});
};
}
}
module.exports = Application;
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
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
compose
方法就是用来合并中间件的,接下来分步骤分析一下这个方法
所有的 middwares
// 中间件1
async (ctx, next) => {
console.log("入口1");
await next();
console.log("出口2");
};
// 中间件2
async (ctx, next) => {
console.log("入口2");
await next();
console.log("出口1");
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
koa 中间件采用洋葱模型,所以我们最后想要的目标中间件应该是这样的
async (ctx, next) => {
console.log("入口1");
console.log("入口2");
await Promise.resolve();
console.log("出口1");
console.log("出口2");
};
1
2
3
4
5
6
7
2
3
4
5
6
7
那么compose
就要做几件事了
- 从 middlewares 末尾开始循环
- 取出了数组的最后一项
- 执行了两个重要的步骤
let next = async () => {
return Promise.resolve();
};
next = createNext(currentMiddleware, next);
1
2
3
4
2
3
4
- 上面两个步骤执行完之后得到了新的 next
next = createNext(
async (ctx, next) => {
console.log("入口2");
await next();
console.log("出口1");
},
async () => {
return Promise.resolve();
}
);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
createNext
执行结束返回了什么
function createNext(middleware, oldNext) {
return async () => {
// await middleware(ctx, oldNext);
// 变成了下面的东西
await (async (ctx, next) => {
console.log("入口2");
await Promise.resolve();
console.log("出口1");
});
};
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 再去继续执行遍历,此时的
next
就变成了上一步的返回值
中间件 1
async (ctx, next) => {
console.log("入口1");
// 把中间件1的 await next() 替换成了下面这一坨
async () => {
await (async (ctx, next) => {
console.log("入口2");
await Promise.resolve();
console.log("出口1");
});
};
console.log("出口2");
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
上面一个简单的 koa 就搞出来了,接下来还要处理一下 context、request、response 和容错
# 编写 request 和 response 文件
创建 request.js 和 response.js 文件
# request 文件
const url = require("url");
// 封装源生 request 操作
// 例如:增加quert方法,快速定位参数。headers 方法快速扩区headers字段
module.exports = {
get query() {
return url.parse(this.req.url, true).query;
},
get url() {
return this.req.url;
}
};
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# response 文件
// 封装源生 response 操作
// 例如:body 方法,统一返回数据到客户端。socket 方法,快速获取 res 中的 socket 对象。
module.exports = {
get body() {
return this._body;
},
set body(data) {
this._body = data;
},
get status() {
return this.res.statusCode;
},
set status(code) {
if (typeof code !== "number") {
throw new Error("statusCode 只能是数字");
}
this.res.statusCode = code;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 编写 context 文件
context 文件主要负责代理 request 和 response
let proto = {};
function delegateSet(property, name) {
proto.__defineSetter__(name, function(val) {
this[property][name] = val;
});
}
function delegateGet(property, name) {
proto.__defineGetter__(name, function() {
return this[property][name];
});
}
// 定义需要代理的属性
let requestSet = [];
let requestGet = ["query", "url"];
let responseSet = ["body", "status"];
let responseGet = responseSet;
requestSet.forEach(item => {
delegateSet("request", item);
});
requestGet.forEach(item => {
delegateGet("request", item);
});
responseSet.forEach(item => {
delegateSet("response", item);
});
responseGet.forEach(item => {
delegateGet("response", item);
});
module.exports = proto;
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
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
# 合并及容错
上面步骤编写完了 context、request、response,接下来重新修改一下我们的 application 文件
修改 createContext 方法
class Application extends EventEmitter {
createContext(req, res) {
let ctx = Object.create(this.context);
ctx.request = Object.create(this.request);
ctx.response = Object.create(this.response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
callback() {
return (req, res) => {
let fn = this.compose();
const ctx = this.createContext(req, res);
return fn(ctx)
.then(() => {
res.end("hello world");
})
.catch(this.onerror(ctx));
};
}
}
module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
添加容错
onerror(ctx) {
return err => {
if (err.code === 'ENOENT') {
ctx.status = 404;
} else {
ctx.status = 500;
}
let msg = err.message;
ctx.res.end(msg);
this.emit('error', err);
};
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 最终代码
application.js
const EventEmitter = require("events");
const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
class Application extends EventEmitter {
constructor() {
super();
this.middlewares = [];
this.context = context;
this.request = request;
this.response = response;
}
use(middleware) {
this.middlewares.push(middleware);
}
listen(...args) {
const server = http.createServer(this.callback());
server.listen(...args);
}
createContext(req, res) {
let ctx = Object.create(this.context);
ctx.request = this.request;
ctx.response = this.response;
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
onerror(ctx) {
return err => {
if (err.code === "ENOENT") {
ctx.status = 404;
} else {
ctx.status = 500;
}
let msg = err.message;
ctx.res.end(msg);
this.emit("error", err);
};
}
compose() {
// console.log(this.middlewares);
// 将所有的middlewares进行递归合并
return async ctx => {
function createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
};
}
let len = this.middlewares.length;
let next = async () => {
return Promise.resolve();
};
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
await next();
};
}
callback() {
return (req, res) => {
let fn = this.compose();
const ctx = this.createContext(req, res);
return fn(ctx)
.then(() => {
res.end("hello world");
})
.catch(this.onerror(ctx));
};
}
}
module.exports = Application;
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75