如何从头实现一个node.js的koa框架

2020-06-17 06:09:13易采站长站整理


// application.js
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
class Application {
/**
* 构造函数
*/
constructor() {
this.callbackFunc;
this.context = context;
this.request = request;
this.response = response;
}
/**
* 开启http server并传入callback
*/
listen(...args) {
let server = http.createServer(this.callback());
server.listen(...args);
}
/**
* 挂载回调函数
* @param {Function} fn 回调处理函数
*/
use(fn) {
this.callbackFunc = fn;
}
/**
* 获取http server所需的callback函数
* @return {Function} fn
*/
callback() {
return (req, res) => {
let ctx = this.createContext(req, res);
let respond = () => this.responseBody(ctx);
this.callbackFunc(ctx).then(respond);
};
}
/**
* 构造ctx
* @param {Object} req node req实例
* @param {Object} res node res实例
* @return {Object} ctx实例
*/
createContext(req, res) {
// 针对每个请求,都要创建ctx对象
let ctx = Object.create(this.context);
ctx.request = Object.create(this.request);
ctx.response = Object.create(this.response);
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
/**
* 对客户端消息进行回复
* @param {Object} ctx ctx实例
*/
responseBody(ctx) {
let content = ctx.body;
if (typeof content === 'string') {
ctx.res.end(content);
}
else if (typeof content === 'object') {
ctx.res.end(JSON.stringify(content));
}
}
}

可以看到,最主要的是增加了createContext方法,基于我们之前创建的context 为原型,使用Object.create(this.context)方法创建了ctx,并同样通过Object.create(this.request)和Object.create(this.response)创建了request/response对象并挂在到了ctx对象上面。此外,还将原生node的req/res对象挂载到了ctx.request.req/ctx.req和ctx.response.res/ctx.res对象上。

回过头去看我们之前的context/request/response.js文件,就能知道当时使用的this.res或者this.response之类的是从哪里来的了,原来是在这个createContext方法中挂载到了对应的实例上。一张图来说明其中的关系:

构建了运行时上下文ctx之后,我们的app.use回调函数参数就都基于ctx了。

下面一张图描述了ctx对象的结构和继承关系:

最后回忆我们的ctx.body方法,并没有直接返回消息体,而是将消息存储在了一个变量属性中。为了每次回调函数处理结束之后返回消息,我们创建了responseBody方法,主要作用就是通过ctx.body读取存储的消息,然后调用ctx.res.end返回消息并关闭连接。从方法中知道,我们的body消息体可以是字符串,也可以是对象(会序列化为字符串返回)。注意这个方法的调用是在回调函数结束之后调用的,而我们的回调函数是一个async函数,其执行结束后会返回一个Promise对象,因此我们只需要在其后通过.then方法调用我们的responseBody即可,这就是this.callbackFunc(ctx).then(respond)的意义。