// response.js module.exports = { get body() { return this._body; }, /** * 设置返回给客户端的body内容 * * @param {mixed} data body内容 */ set body(data) { this._body = data; }, get status() { return this.res.statusCode; }, /** * 设置返回给客户端的stausCode * * @param {number} statusCode 状态码 */ set status(statusCode) { if (typeof statusCode !== 'number') { throw new Error('statusCode must be a number!'); } this.res.statusCode = statusCode; } };
也很简单。status读写方法分别设置或读取this.res.statusCode。同样的,这个this.res是挂载的node原生response对象。而body读写方法分别设置、读取一个名为this._body的属性。这里设置body的时候并没有直接调用this.res.end来返回信息,这是考虑到koa当中我们可能会多次调用response的body方法覆盖性设置数据。真正的返回消息操作会在application.js中存在。
然后我们创建context.js文件,构造context对象的原型:
// context.js module.exports = { get query() { return this.request.query; }, get body() { return this.response.body; }, set body(data) { this.response.body = data; }, get status() { return this.response.status; }, set status(statusCode) { this.response.status = statusCode; } };
可以看到主要是做一些常用方法的代理,通过context.query直接代理了context.request.query,context.body和context.status代理了context.response.body与context.response.status。而context.request,context.response则会在application.js中挂载。
由于context对象定义比较简单并且规范,当实现更多代理方法时候,这样一个一个通过声明的方式显然有点笨,js中,设置setter/getter,可以通过对象的__defineSetter__和__defineSetter__来实现。为此,我们精简了上面的context.js实现方法,精简版本如下:
let proto = {}; // 为proto名为property的属性设置setter function delegateSet(property, name) { proto.__defineSetter__(name, function (val) { this[property][name] = val; }); } // 为proto名为property的属性设置getter function delegateGet(property, name) { proto.__defineGetter__(name, function () { return this[property][name]; }); } // 定义request中要代理的setter和getter let requestSet = []; let requestGet = ['query']; // 定义response中要代理的setter和getter let responseSet = ['body', 'status']; let responseGet = responseSet; requestSet.forEach(ele => { delegateSet('request', ele); }); requestGet.forEach(ele => { delegateGet('request', ele); }); responseSet.forEach(ele => { delegateSet('response', ele); }); responseGet.forEach(ele => { delegateGet('response', ele); }); module.exports = proto;
这样,当我们希望代理更多request和response方法的时候,可以直接向requestGet/requestSet/responseGet/responseSet数组中添加method的名称即可(前提是在request和response中实现了)。
最后让我们来修改application.js,基于刚才的3个对象原型来创建request, response, context对象:
// 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对象上。