koa 源码分析
Koa===Application ,创建一个application实例
constructor(options) {
super(); // Application 继承了events对象。
/** ... */
this.middleware = []; // 用来存放中间件
this.context = Object.create(context); // 引入context对象
this.request = Object.create(request); // 引入koa 的request对象
this.response = Object.create(response); // 引入koa的response对象
}
koa 核心代码
Koa 是个web 框架,它核心是提供一个web服务,入口函数我们是app.listen()
- listen()
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
listen代码很简单,只有三行;本质上还是利用了原生的http package 启动了一个http server,如果不用koa框架,我们用原生的写法如下:
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
res.setHeader('X-Foo', 'bar');
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('ok');
});
所以看到这里我们就能猜到this.callback(),应该返回一个(req,res)=>{}这样的一个回调函数。
- callback()
callback() {
const fn = compose(this.middleware); // 中间件的处理,这里可以调到下一章看中间件机制,这里返回一个(ctx,next)=>{}这样的函数。
if (!this.listenerCount('error')) this.on('error', this.onerror); // 如果没有添加错误的监听回调,这里会添加一个默认的。
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res); // 创建一个ctx对象
return this.handleRequest(ctx, fn); // 处理请求和返回响应
};
return handleRequest; // 这里返回一个函数,和我们在listen中的猜想是一样的。
}
- handleRequest()
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
我们在用Koa框架的时候,我们把我们想要的返回值,赋值给ctx.body:
ctx.body='hello world'; // 返回一个字符串
ctx.body={hello:'world'}; // 返回一个json对象
这个部分的处理是在handleResponse处理的,handleResponse调用了respond();
function (){
//...
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
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
中间件的原理
koa源码的另外一个亮点就是它的中间件机制,我们可以使用use()添加一个中间件:
use(fn){
/*
...判断 是否是generator函数,并做出warn 并做转换
*/
this.middleware.push(fn); // 核心代码只有这一行,middleware= [];
return this;
}
洋葱模型
中间件的原理是基于洋葱模型的,什么是洋葱模型,这里盗了一张图,来解释一下。

koa主要利用了async和await的特性,执行到下一个中间件的地方暂停执行,执行完后再回复继续执行。
中间件的使用
我们从上面可以看到use只是把中间件保存下来,然后在callback()函数里面用到了
const fn = compose(this.middleware);
compose 引入的一个第三方package koa-compose, 我们看一个这个包的关键代码
function compose(middleware) {
// 返回了一个和中间件声明一样的函数
return function (context, next) {
// last called middleware #
let index = -1 // 记录已经执行到中间件的索引
return dispatch(0) // 调用dispatch(0) 从第一个中间件开始执行
// 声明了一个内部函数dispatch
function dispatch(i) {
// 如果一个中间件执行两次,会抛出错误
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, dispatch.bind(null, i + 1)));// 中间件定义的时候的形参next,传入的实参实参上是dispatch(i+1), 递归调用dispatch. 并保证结果返回的是个Promise
} catch (err) {
return Promise.reject(err)
}
}
}
}