koa自身的代码比较少,但是有很多依赖的npm包,有不少是在jshttp这个project里(比如on-finished)。
版本2.7.0
先来看一个使用koa的例子
const Koa = require('koa');
const fs = require('fs');
const app = new Koa();
function render(page) {
return new Promise((resolve, reject) => {
let viewUrl = `./view/${page}`;
fs.readFile(viewUrl, 'binary', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
async function route(url) {
let view = '404.html';
switch (url) {
case '/':
view = 'index.html';
break;
case '/index':
view = 'index.html';
break;
case '/404':
view = '404.html';
break;
default:
break;
}
let html = await render(view);
return html;
}
app.use(async ctx => {
let url = ctx.request.url;
let html = await route(url);
ctx.body = html;
});
app.listen(3000);
比较简单,应该都能看得懂。这一段简单的代码涵盖了一些平时不太注意的点,比如app.listen、ctx.body、app.use等,基本上讲完这几个,koa的本体就差不多了。
koa一共就4个文件,大家都知道,上面提到的app开头的函数都在application.js里,ctx.body在response.js里,我们一个一个来看
1.application.js
app继承自node的EventEmitter类,app的构造函数中引用了其他三个文件,并创建了成员变量(其实后面只有一处用到)
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
listen函数用node的http模块的createServer函数创建了server实例,并进行监听
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
调用createServer的时候,传入了this.callback(),我们来看一下。
callback返回了一个函数,这个函数先调用了createContext,然后返回了handleRequest成员函数
callback() {
// 处理中间件,等一下看compose和this.middleware
const fn = compose(this.middleware);
// 错误处理,listenerCount是EventEmitter类的函数
if (!this.listenerCount('error')) this.on('error', this.onerror);
// 传递给createServer的就是下面这个函数
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
// 这里等到response再看
const handleResponse = () => respond(ctx);
// 给请求结束增加一个回调,这个onerror是ctx的onerror,不是app的onerror
onFinished(res, onerror);
// 等一下看这个
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
createContext(req, res) {
// 每次请求,ctx都是一个新的对象
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
// 原生的req和res
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
// koa生成的request和response
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
return context;
}
看到这里app就剩下middleware了,middleware的输入在app.use里,只是一个push(如果是用generator写的,会先经过koa-convert转换),然后就是callback中koa-compose的处理,koa-compose就是将传入的数组形式的中间处理函数都用Promise包了,然后组织成一层套一层的形式,可以自己看一下。
2.context.js
提供了一个相对具体的onerror,一个cookie读写属性,另外全是代理了request和response的方法和属性,代理方法是使用delegates这个包,这个包的核心是__defineGetter__
和__defineSetter__
这两个方法,当然现在都建议用Object.defineProperty了。
3.request
没什么和流程相关的代码,基本都是和http请求相关的内容,可以扫一眼,遇到问题了再看
4.response
body和set可以看一看。
set用的是setHeader方法,和writeHeader有挺多不一样的地方,具体可以查阅node文档。
body是个容易迷惑的地方,body从头看到为会发现最后也没调用res.end或者res.write,其实是在app里调用的,就是上面那个说到response再看的地方,自己看一下respond函数,会发现是调用了res.end方法。
function respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
if (!ctx.writable) return;
const res = ctx.res;
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
if (null == body) {
if (ctx.req.httpVersionMajor >= 2) {
body = String(code);
} else {
body = ctx.message || String(code);
}
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// responses
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-router之后回来写的
koa的本体代码不多,但是看了它的中间件以后发现还是需要了解一下哪些能力是本体提供的,有时候中间件调了一下ctx.xxx你都不知道这个xxx是哪里来的,所以一上来可以不用把本体看的特别熟悉,遇到了问题可以再回头来看,顺便所有的中间件都存到了middleware里面,调试的时候很方便查看