基本目录结构如下:
tree ./koa
./koa
├── application.js
├── context.js
├── request.js
└── response.js
1
2
3
4
5
6
2
3
4
5
6
// application.js
// Koa的入口文件为application.js
const http = require('http');
const context = require('./context');
const request = require('./request');
const response = require('./response');
class Koa {
constructor() {
this.callbackFn;
this.middlewares = [];
// 将这些变量统一放到实例属性上,方便调用
this.context = context;
this.request = request;
this.response = response;
}
// 常用的use方法
use(cb) {
this.middlewares.push(cb);
}
// 常用的监听服务端口方法
listen() {
// bind方法绑定this
const server = http.createServer(this.handleRequest.bind(this));
// 剩余运算符接收传递的参数(包括端口号和服务启动后的回调函数)
server.listen(...arguments);
}
handleRequest(req, res) {
res.statusCode = 404; // 默认页面找不到
const ctx = this.createContext(req, res);
// compose => 组合
const composeMiddleware = this.compose(ctx, this.middlewares);
// 当回调函数执行后,ctx.body值就会发生变化
// 当此promise执行完成后,再去执行res.end()
composeMiddleware.then(() => {
const body = ctx.body;
if (typeof body === 'undefined') {
res.end('Not Found');
} else if (typeof body === 'string') {
// 响应
res.end(body);
}
});
}
// 创建上下文对象
createContext(req, res) {
// 希望ctx可以拿到context的属性,但是不修改context
// req是http模块原生的
// request对象是koa自己实现的,response也同理
const ctx = Object.create(this.context);
ctx.request = Object.create(this.request);
ctx.req = ctx.request.req = req;
ctx.response = Object.create(this.response);
ctx.res = ctx.response.res = res;
return ctx; // 返回上下文对象
}
// compose方法返回一个Promise
compose(ctx, middlewares) {
function dispatch(index) {
// 越界说明都执行完毕了
if (middlewares.length === index) return Promise.resolve();
// 先取出第一个中间件,让其执行,将索引递增,调用next,就是将下一个中间件,继续执行
const middleware = middlewares[index];
// () => dispatch(index + 1)就是next方法
// 返回一个Promise
// 递归创建 套起来的promise
return Promise.resolve(middleware(ctx, () => dispatch(index + 1)));
}
// dispatch方法返回的也是Promise
return dispatch(0); // 让第一个中间件执行
}
}
module.exports = Koa;
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
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
// context.js
// 源码里也叫proto
const proto = {};
// proto.url = proto.request.url
// 获取proto.url,就去找proto.request的url
// 取值代理
function defineGetter(property, name) {
// 自定义获取器 代理
// __defineGetter__原生方法
// 取值的时候,会调用回调函数
// 获取proto.url,就去找proto.request的url
proto.__defineGetter__(name, function () {
// this指向proto,也就是ctx
return this[property][name];
});
}
// 针对ctx.body = ctx.response.body做代理
// 设置值代理
// ctx.body = 'hello' 就相当于 ctx.response.body = 'hello'
function defineSetter(property, name) {
proto.__defineSetter__(name, function (value) {
this[property][name] = value;
});
}
defineGetter('request', 'url');
defineGetter('request', 'path');
defineGetter('response', 'body');
// 设置代理:ctx.body 等价于 ctx.response.body
defineSetter('response', 'body');
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
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
// request.js
const url = require('url');
// const qs = require('querystring');
const request = {
get url() {
// 我们调用url方法的方式:ctx.request.url,因此this指向ctx.request
// 而ctx.request.req.url是可以获取到url的,因此可以写成this.req.url
return this.req.url;
},
// url是带有查询字符串的,path不带
get path() {
// 通过解析this.req.url来获取path属性
return url.parse(this.req.url).pathname;
},
// get query() {
// console.log(url.parse(this.req.url));
// return url.parse(this.req.url).path;
// }
};
module.exports = request;
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
// response.js
const response = {
// 调用的时候:ctx.response.body = 'hello'
set body(value) {
this.res.statusCode = 200; // 只要调用了ctx.body = 'xxx'就会成功
this._body = value;
},
get body() {
return this._body;
},
};
module.exports = response;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
测试用例
// Koa源码中入口引用的也是lib/application.js
const Koa = require('./koa/application');
const app = new Koa();
app.use((ctx) => {
console.log(ctx.req.url); // /a
console.log(ctx.request.url); // /a
console.log(ctx.request.req.url); // /a
console.log(ctx.url); // /a ctx会代理ctx.requets上的属性
console.log(ctx.req.query);
console.log(ctx.req.path); // undefined ctx.req = req
console.log(ctx.request.path); // /a ctx.request是koa自己封装的属性
console.log(ctx.request.req.path); // undefined ctx.request.req = req
console.log(ctx.path); // /a 用ctx来代理一下ctx.request属性
ctx.body = 'hello koa 666';
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
模拟洋葱模型:
function app() {}
app.middlewares = [];
app.use = function (callback) {
app.middlewares.push(callback);
};
// 模拟洋葱模型
app.use((ctx, next) => {
console.log('hello koa1');
next();
console.log('hello koa2');
});
app.use((ctx, next) => {
console.log('hello koa3');
next(); // 假如这里没有next,将不会向下执行了
console.log('hello koa4');
});
app.use((ctx, next) => {
console.log('hello koa5');
next();
console.log('hello koa6');
});
function dispatch(index) {
// 防止越界
if (app.middlewares.length === index) return;
// 先取出第一个中间件,让其执行,将索引递增,调用next,就是将下一个中间件,继续执行
const middleware = app.middlewares[index];
// () => dispatch(index + 1)就是next方法,dispatch(index + 1)即执行下一下中间件
// 执行取出的中间件
middleware({}, () => dispatch(index + 1));
}
// 让第一个中间件执行
dispatch(0);
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
Koa 洋葱模型实现依赖 compose 方法:
const Koa = require('./koa/application');
const app = new Koa();
// koa可以使用async await
const log = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ok了');
resolve();
}, 1000);
});
};
// 这些中间件会形成一个promise
app.use(async (ctx, next) => {
console.log('hello koa1');
// await会等待下一个中间件执行完毕后再继续执行
await next();
console.log('hello koa2');
});
app.use(async (ctx, next) => {
console.log('hello koa3');
await log(); // 调用await就会等待异步操作执行完毕后再继续执行
await next();
console.log('hello koa4');
});
app.use(async (ctx, next) => {
console.log('hello koa5');
await next();
console.log('hello koa6');
});
app.listen(3000, () => {
console.log('server run 3000');
});
// 输出如下:
/*
hello koa1
hello koa3
ok
hello koa5
hello koa6
hello koa4
hello koa2
*/
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
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