本文代码放在 Github。
一、关于 node http 的用法
node http 的用法很简单,http 的每次请求,都会带有 request 和 response 两个对象。
1 2 3 4 5 6 7 8 9 10 11
| const server = http.createServer((request, response) => { console.log(request.method); console.log(request.url); console.log(request.headers['cookie']); console.log(request.httpVersion); response.write('response data.'); response.end(); }); server.listen(3000);
|
通过 request 可以获取到请求头相关的东西,比如请求头的 method、url、http header 等等,另外,request 继承自可读流,因此 request 具备可读流的所有特点,比如可以监听 on(‘data’) 获取到请求体的内容,也可以on(‘end’) 监听流的结束,代码如下:
1 2 3 4 5 6 7 8
| const data = []; request.on('data', function (dataBuffer) { data.push(dataBuffer); }); request.on('end', function () { const buffer = Buffer.concat(data).toString(); console.log(buffer); });
|
response 继承自可写流,拥有可写流的所有特点,比如 write、end 、close操作,
通过 response.wirte(‘响应数据’) + response.end() 可以向客户端响应数据。
response.end(‘xxx’) 相当于 response.write(‘xxx’) + response.close();
代码如下:
1 2
| response.write('response data.'); response.end();
|
二、koa 是基于 node http 的
koa 是一个 http 框架,因此,koa 是基于 node http 模块的。
最简单的 koa 实现如下(其实内部就是封装了 http):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Koa { constructor(fn) { this.fn = fn; } listen(...args) { const server = http.createServer((req, res) => { this.fn(req, res); }); return server.listen(...args); } } function handleHttp(req, res) { res.end('response data.'); } const koa = new Koa(handleHttp); koa.listen(3000);
|
三、koa 的上下文 Context
1、在 koa 的Context 中,实现了如下功能:
- 1)分别定义了 req和res 属性来保留原生的 request和response 对象,因此我们可以通过 ctx.req 和 ctx.res 访问到原来的 request和response 对象。
- 2)分别定义了 request 和 response 属性,用于扩展 原生的request和response 对象。
- 3)实现了委托,把 context 上的属性,委托给 request 或 response 对象,比如,当我访问 ctx.body=xxxx 的时候,其实访问的是 response.body=xxx,当我访问 ctx.method 的时候,其实访问的是 request.method。
2、委托的实现原理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| function Delegator(proto, target) { if (!(this instanceof Delegator)) { return new Delegator(proto, target); } this.proto = proto; this.target = target; }
Delegator.prototype.getter = function (name) { const proto = this.proto; const target = this.target; proto.__defineGetter__(name, function () { return this[target][name]; }); return this; }; Delegator.prototype.setter = function (name) { const proto = this.proto; const target = this.target; proto.__defineSetter__(name, function (value) { return this[target][name] = value; }); return this; }; Delegator.prototype.access = function (name) { return this.getter(name).setter(name); };
Delegator.prototype.method = function (name) { const proto = this.proto; const target = this.target; proto[name] = function () { return this[target][name].apply(target, arguments); }; return this; };
const context = { request: { a: 123, fn: function () { return 'fn'; } } }; Delegator(context, 'request') .getter('a') .setter('a') .method('fn');
console.log(context.a); console.log(context.fn());
|
输出如下:
3、Context 在 koa 的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| const request = { get method() { return this.req.method; }, }; const response = { get body() { return this._body; }, set body(val) { this._body = val; }, }; const context = {}; Delegator(context, 'request').getter('method'); Delegator(context, 'response').access('body');
class Koa { constructor(fn) { this.fn = fn; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } listen(...args) { const server = http.createServer((req, res) => { const context = this.createContext(req, res); Promise.resolve(this.fn(context)).then(() => { let body = 'koa response'; if (context.body) { body = context.body; } res.write(body); res.end(); });
}); return server.listen(...args); } createContext(req, res) { const context = Object.create(this.context); context.res = res; context.req = req; context.request = Object.create(this.request); context.response = Object.create(this.response); context.req = request.req = response.req = req; context.res = request.res = response.res = res; return context; } } function handleHttp(ctx) { console.log(ctx.method); ctx.body = 'reponse data.'; } const koa = new Koa(handleHttp); koa.listen(3000);
|
四、middleware(中间件)洋葱模型
koa 的核心思想在于基于中间件的洋葱模型,koa 通过一个 compose 函数,把一个一个的中间件串联起来,组合成了一个大的函数,在每个中间件里,通过 next() 执行下一个中间件。
引用官方的一张洋葱模型图,如下:
compose 的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function compose(middlewares) { return function (context, next) { function dispatch(i) { let fn = middlewares[i]; if (i === middlewares.length) fn = next; if (!fn) { return Promise.resolve(); } try { return Promise.resolve(fn(context, () => { return dispatch(i + 1); })); } catch (e) { return Promise.reject(e); } } return dispatch(0); }; }
const middlewares = []; async function a(ctx, next) { console.log('a1'); const r = await next(); console.log(r); console.log('a2'); } async function b(ctx, next) { console.log('b1'); await next(); console.log('b2'); return 'b'; } middlewares.push(a); middlewares.push(b); compose(middlewares)({}).then(v => { console.log('ddd'); });
|
以上代码的执行顺序如下:
1 2 3 4 5 6
| a in b in b out b a out end
|
五、在 koa 应用中间件洋葱模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| const http = require('http'); const EventEmitter = require('events')
function compose(middlewares) { return function (context, next) { function dispatch(i) { let fn = middlewares[i]; if (i === middlewares.length) fn = next; if (!fn) { return Promise.resolve(); } try { return Promise.resolve(fn(context, () => { return dispatch(i + 1); })); } catch (e) { return Promise.reject(e); } } return dispatch(0); }; }
function Delegator(proto, target) { if (!(this instanceof Delegator)) { return new Delegator(proto, target); } this.proto = proto; this.target = target; }
Delegator.prototype.getter = function (name) { const proto = this.proto; const target = this.target; proto.__defineGetter__(name, function () { return this[target][name]; }); return this; }; Delegator.prototype.setter = function (name) { const proto = this.proto; const target = this.target; proto.__defineSetter__(name, function (value) { return this[target][name] = value; }); return this; }; Delegator.prototype.access = function (name) { return this.getter(name).setter(name); };
Delegator.prototype.method = function (name) { const proto = this.proto; const target = this.target; proto[name] = function () { return this[target][name].apply(target, arguments); }; return this; };
const request = { get method() { return this.req.method; }, }; const response = { get body() { return this._body; }, set body(val) { this._body = val; }, }; const context = {}; Delegator(context, 'request').getter('method'); Delegator(context, 'response').access('body');
class Koa extends EventEmitter { constructor() { super(); this.middlewares = []; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); } listen(...args) { const server = http.createServer((req, res) => { this.handleHttp(req, res); }); return server.listen(...args); } use(fn) { this.middlewares.push(fn); } handleHttp(req, res) { const middleware = compose(this.middlewares); const context = this.createContext(req, res); middleware(context).then(() => { let body = 'koa response'; if (context.body) { body = context.body; } res.write(body); res.end(); }); } createContext(req, res) { const context = Object.create(this.context); context.res = res; context.req = req; context.request = Object.create(this.request); context.response = Object.create(this.response); context.req = request.req = response.req = req; context.res = request.res = response.res = res; return context; } } const koa = new Koa(); async function middlewareA(ctx, next) { console.time('responseTime'); console.log('middlewareA in'); console.log(ctx.method); await next(); console.log(ctx.body); console.log('middlewareA out'); console.timeEnd('responseTime'); } function middlewareB(ctx, next) { console.log('middlewareB in'); ctx.res.setHeader('Content-Type', 'text/html;charset=utf-8'); ctx.body = 'reponse data.'; console.log('middlewareB out'); } koa.use(middlewareA); koa.use(middlewareB); const server = koa.listen(3000); server.on('listening', function () { console.log('listening on localhost:3000'); });
|
六、代码放在 github 上
https://github.com/SimpleCodeCX/simple-koa