本文将用一个饭店的例子帮助理解 Koa 做了哪些事情。
先看下 Koa 给的官方 Demo:
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
我们运行这几行代码,就可以在浏览器打开localhost:3000,页面会出现 Hello Koa 的文字。现在我们就来分析一下这段代码都做了哪些事情。
为了方便理解,我们把抽象的东西实体化,假设服务是一个饭店,饭店里有服务员,厨师等员工,有各种菜品,然后等待顾客上门消费,随后会套用这个例子来理解。
首先分析一下前两行代码
const Koa = require('koa');
const app = new Koa();
这两行代码只是简单的引入 Koa,然后 new 一个实例,那么这个 new Koa()都做了哪些事情呢?
通过 package.json 可以看出 Koa 的入口文件是 lib/application.js,在这个文件中我们可以看到,该文件导出的其实是 Application 这个类,那么紧接着看下这个类的构造函数 constructor
constructor() {
super();
this.proxy = false;
this.middleware = [];
this.subdomainOffset = 2;
this.env = process.env.NODE_ENV || 'development';
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
只是简单的做了些初始化。没什么好说的,接着往下看。
app.use(ctx => {
ctx.body = 'Hello Koa';
});
在 Application 中找到 use 方法的定义
use(fn) {
// ...
this.middleware.push(fn);
return this;
}
use 接收了一个函数作为参数,并把函数 push 进了 middleware,而我们在 constructor 里得知 middleware 就是一个数组。那么只能接着往下看。
到了最后一行代码
app.listen(3000);
在 application 中我们可以找到 listen 的定义
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
这个方法的定义也很简单,是用 node 的 http 模块启动了一个 server,关键在传给 http.createServer 的参数,这个参数是个方法,在查看方法callback 的定义之前,我们先看下 http.createServer 。
createServer 返回一个 server,接收一个函数,这个函数会在有请求的时候执行,函数会接收 http.IncomingMessage 和 http.ServerResponse,也就是我们常见的 request 和 response。
server.listen 方法就开始监听,一旦有请求映射到 3000 端口,就会执行传入到 createServer 的函数。
现在可以推测,callback 必定是返回一个函数,函数接收 request 和 response 两个参数。我们去看下 callback 的定义。
callback() {
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
callback 返回 handleRequest,正好是一个函数,接收 req 和 res 两个参数。在这里我们终于又看到了 middleware,之前使用 use 将一个函数 push 进去后,就不知道做了什么了。这里发现是把它传进了 handleRequest 里(compose 是把一组函数封装成 Promise,一次调用,这里先不详解),那么先跳过 createContext,直接看 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);
}
在 handleRequest 里我们发现又定义了两个新函数,onerror 和 handleResponse 。handleResponse 作为 fnMiddleware 到回调,也就是运行完所有的 middleware 后会执行 handleResponse。看 respond 的定义。
function respond(ctx) {
// ...
const res = ctx.res;
let body = ctx.body;
// ...
res.end(body);
}
respond 里看到了 res.end(body) ,这也说明请求倍返回了,也就是我们在浏览器看到的 Hello Koa。
那么我们套用之前饭店的例子梳理一下,new Koa 就表示我们的饭店成立了,而 app.use 就表述我们新增了员工(其实职位更贴切,但是员工更方便理解,就假设每个职位就一个人),而 middleware 就是我们的员工列表。app.listen 表示我们在门口挂上了“营业中”的牌子,等待顾客光临。当有顾客进来时,员工就要工作了,让顾客就坐,点餐,厨师做饭,然后上餐… 这就是所有 middleware 开始一次执行。最后顾客吃完离开,也就是 respond。还有 onerror,相当于保安和经理,用来处理意外情况,比如顾客菜里吃出了蟑螂,顾客钱不够,就需要保安出来锤一顿。
参照我们饭店的例子,现在我们在从头梳理一下,我们 new 了一个 Koa 对象,然后像 middleware 里添加了一个函数,最后开始 listen。当有人请求我们所 listen 的端口时,我们就会把所有的 middleware 执行一遍,最后执行 respond ,(这里我们忽略的 context 和其他内容),将结果返回给那个请求的人。