# 关于listen做了什么
# listen
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
1
2
3
4
5
2
3
4
5
我们看到,listen
部分的源码很简单,调用了node原生的http
模块,然后再调用原生的listen
所以,这里的关键部分是this.callback()
,我们来看看这个函数做了什么
# callback
callback() {
//使用compose函数对中间件处理,***这个函数是koa的重点之一***
const fn = compose(this.middleware);
//监听错误事件
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
// 默认返回404,如果后面有调用ctx.body之类的方法,会把status改成200
res.statusCode = 404;
const ctx = this.createContext(req, res); //创造ctx
const onerror = err => ctx.onerror(err); //未来用来处理错误的函数
const handleResponse = () => respond(ctx); //未来用来返回结果的函数
onFinished(res, onerror); //当res结束时候执行回调
//执行函数,并将结果交给handleRespons
return fn(ctx).then(handleResponse).catch(onerror);e
};
return handleRequest;
}
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
- 这一部分有点多,我们分别来看看每个函数的作用
# compose(第3行)
function compose (middleware) {
// 检查midddleware类型,以及数组每一项是不是函数
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
//当调用多次next会报错
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
//先把当前位置的函数取出来
let fn = middleware[i]
if (i === middleware.length) fn = next
//如果递归到这一层没有函数了,则返回
if (!fn) return Promise.resolve()
try {
// *** 递归调用,把下一个函数作为函数的第二个参数传入 ***
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
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
看到这部分代码,不知道你是否还记得我们之前说的前置知识点二,这里其实就是一个升级版,
多了一层类型检查
和Promise
处理,这些代码也就是Koa洋葱模型中间件
的关键部分啦.
# createcontext(第10行)
createContext(req, res) {
//做一系列属性的挂载,使context拥有requset,以及response上面的属性
const context = Object.create(this.context); //把this.context赋值给context变量
//先把this.request赋值给context.request变量,然后单拎出来
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
//保存原生的req,res在context里
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
//另外可能需要的一些属性
context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这里的目的就是创造ctx
,这里其实就是做了一系列属性间的相互挂载,为了能够使context
拥有requset
,以及response
上面的属性,并且比较有意思的是保护了原生的req,res
不受到污染.
# handleResponse(第15行)
function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
const res = ctx.res;
if (!ctx.writable) return;
let body = ctx.body;
const code = ctx.status;
//...已省略,为了处理一些特殊情况
// 用res.end响应 ***关键部分***
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
// 返回json格式
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这一部分,也就是如果不发生error
,函数正常执行的最后一部分,发挥主要作用的其实就是在13-23行的代码
,在前面compose
的闭包函数执行过后,
对不同的ctx.body
用res.end
做不同的处理,返回不同结果,把结果打印到页面上.
至此,一次koa调用,正式结束.