# 深入 Koa 原理
通过上一篇文章,我们已经了解到 Koa 是个非常精简的框架,实现起来难度并不大,这一章我们就一起来手写一个 koa 吧!
编写 application 文件
编写 request 和 response 文件
编写 context 文件
# 编写 application 文件
Application 文件是 koa 的核心,所有逻辑都会经过该文件调度。
# 开启 Web 服务器
首先,koa 作为一个 web 服务器框架,开启一个 web 服务器是最基本的方法。
封装 listen 方法开启服务器(底层调用 http.createServer)。
在 http.createServer 回调函数中调用 callback 方法,执行中间件。
中间件执行不报错,会调用 respond 方法,对返回及结果进行操作。
中间件执行报错,会调用 onerror 方法,输出错误信息到客户端。
//node 常见的事件模型就是我们常见的订阅发布模式,核心API采用的就是异步事件驱动
//所有可能触发事件的对象都是一个继承自Emitter类的子对象,简单来说就是Node帮我们实现了订阅发布模式
const Emitter = require('events');
const http = require('http');
const request = require("./request");
const response = require("./response")
const context = require('./context')
const Stream = require('stream');
class Application extends Emitter {
constructor() {
super();
this.middlewares = [];
this.request = Object.create(request);
this.response = Object.create(response);
this.context = Object.create(context)
}
callback() {
return (req, res) => {
let fn = this.compose();
const ctx = this.createContext(req, res);
let respond = () => this.resposeBody(ctx)
let error = (err) => this.onerror(err, ctx)
return fn(ctx).then(respond).catch(error)
}
}
use(middleware) {
this.middlewares.push(middleware)
//链式调用
return this;
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
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
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
# 创建 context 代理请求和响应
为了更方便用户的操作,koa 将请求和响应两个对象进行了代理,通过创建 context 对象,掌管整个请求和响应。
class Application extends Emitter {
createContext(req, res) {
//koa就是对node底层的转接
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;
}
}
module.exports = Application;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 封装中间件执行逻辑
koa-compose 接收一个 middleware 的集合,并返回一个函数用来执行所有 middleware。代码执行的顺序是洋葱式的代码。
class Application extends Emitter {
compose() {
if (!Array.isArray(this.middlewares)) throw new TypeError("middlewares must be array")
for (let fn of this.middlewares) {
if (typeof fn !== "function") throw new TypeError("middleware must be composed of function")
}
return async(ctx) => {
let next = async() => {
return Promise.resolve();
}
let len = this.middlewares.length - 1;
for (let i = len; i >= 0; i--) {
let currentMiddle = this.middlewares[i];
next = creatNext(currentMiddle, next)
}
await next();
function creatNext(currentMiddle, nextMiddle) {
return async() => {
await currentMiddle(ctx, nextMiddle);
}
}
}
}
}
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
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
# 中间件执行完成后的操作
中间件执行完成后会执行以下方法。
- responseBody 向客户端输出数据之前,进行数据的处理。
- onerror 如果中间件出错,则做出对应操作。
class Application extends Emitter {
onerror(err, ctx) {
console.lof(ctx)
if (err.code == "ENOENT") {
ctx.status = 404;
} else {
ctx.status = 500;
}
let msg = err.message || "koa error";
ctx.res.end(msg);
this.emit("error", err);
}
resposeBody(ctx) {
const res = ctx.res;
let body = ctx.body;
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
}
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
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
# 编写 request 和 response 文件
Request 和 Response 文件是对请求和响应的一层浅封装,提供一套更加方便的请求响应处理方法。
# 编写 request 文件
var 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
12
2
3
4
5
6
7
8
9
10
11
12
# 编写 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(statusCode) {
if (typeof statusCode !== "number") {
throw new TypeError("statusCode must be number")
}
this.res.statusCode = statusCode;
}
}
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。
- 使用defineSetter方法代理 set 请求。
- 使用defineGetter方法代理 get 请求。
//负责代理,把上面的方法代理到context上面
let proto = {};
//老式的代理
//__defineGetter__
//__defineSetter__
function delegateGet(property, name) {
proto.__defineGetter__(name, function(val) {
return this[property][name];
})
}
function delegateSet(property, name) {
proto.__defineSetter__(name, function(val) {
this[property][name] = val
})
}
let requeSet = [];
let requeGet = ["query"];
let responseSet = ["body", "status"];
let responseGet = responseSet;
requeSet.forEach(ele => {
delegateSet("request", ele)
})
requeGet.forEach(ele => {
delegateGet("request", ele)
})
responseSet.forEach(ele => {
delegateSet("response", ele)
})
responseGet.forEach(ele => {
delegateGet("response", ele)
})
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
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
# 总结
仅供参考