精华 Koa中间件功能源码阅读
发布于 10 年前 作者 CoderIvan 6792 次浏览 最后一次编辑是 9 年前 来自 分享

Koa

前言

由于项目需要,要开发一个UDP服务器,没找到合适的第三方库,故需要自己实现

哪怎么个实现?我当时是没什么想法,但我们都是站在巨人的肩膀上的,所以这次我选择站在TJ大神的肩膀上

  • 一开始是模仿的Express,使用尾调用的方式实现了过滤器和路由器特色

  • 后面Koa出来了,NodeJS也原生支持部分ES6,就开始模仿Koa,加入了Generator和Promise,模仿了this的传递

  • 后面Koa2也出来了,脱离了co.js,减少了对第三方库的依赖和更容易平滑升级到ES7

正文

使用一个新框架,我一般都是直接抄一个Demo跑一下,确定环境和依赖包的正常,以下是Koa官网的Demo

Demo:

var koa = require('koa')
var app = koa()
app.use(function*() {
 this.body = 'Hello World'
})
app.listen(3000)

代码很少,只有app.useapp.listen

  • app.use用于加载中间件
  • app.listen启动服务器

继续找对应的代码实现

app.use:

app.use = function(fn){
 if (!this.experimental) {
 // es7 async functions are allowed
 assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
 }
 debug('use %s', fn._name || fn.name || '-');
 this.middleware.push(fn);
 return this;
};

很简单,就是往中间件的数组中添加中间件,当然这个中间件必须是generator函数

app.listen:

app.listen = function(){
 debug('listen');
 var server = http.createServer(this.callback());
 return server.listen.apply(server, arguments);
};

这就是我们一开始接触NodeJS时的Demo,除了一个东西this.callback(),这替代了function(req, res){},说明this.callback()最终会返回一个供http.createServer调用的function

继续看看Koa是怎么生成这个function(req, res){}

app.callback:

app.callback = function(){
 var fn = this.experimental
 ? compose_es7(this.middleware)
 : co.wrap(compose(this.middleware));
 var self = this;
 if (!this.listeners('error').length) this.on('error', this.onerror);
 return function(req, res){
 res.statusCode = 404;
 var ctx = self.createContext(req, res);
 onFinished(res, ctx.onerror);
 fn.call(ctx).then(function () {
 respond.call(ctx);
 }).catch(ctx.onerror);
 }
};
  • 调用compse函数,将中间件数组传入,返回generator函数
  • 使用co.wrap包装,生成代理方法,该方法将返回Promise对象
  • function(req, res){}中执行fn.call(ctx),fn将完成所有中间件的处理

compose:

function compose(middleware){
 return function *(next){
 var i = middleware.length;
 var prev = next || noop();
 var curr;
 while (i--) {
 curr = middleware[i];
 prev = curr.call(this, prev);
 }
 yield *prev;
 }
}
function *noop(){}
  • curr.call(this, prev)绑定this上下文对象,从上面代码中知道,这个this由外部特别指定的,然后在这再绑定到中间件中
  • curr.call(this, prev)绑定prev参数,这个参数总是下一个中间件,即第i个中间作为形参next,传入第i-1的中间件中
  • 最终以 midddle[i-1](middle[i])把所有中间件串联起来
  • 所以每个next,实际上就是一个Generator的迭代器,在中间件中使用yield next来调用下一个中间件

总结

至此一个Koa的执行代码已经列出来了,不关注细节,把上述代码组合简化一下:

var server = http.createServer(function(req, res){
 var ctx = self.createContext(req, res)
 var gen = compose(this.middleware)
 var prom = co.wrap(gen)
 prom.call(ctx).then().catch()
 }
})
server.listen()

Koa中间件功能的特性就在上面4行代码中了

  • self.createContext(req, res) 创建上下文
  • compose(this.middleware) 将中间组合起来,最终返回一个Generator函数
  • co.wrap(gen),对Generator函数进行包装,返回自动遍历Generator函数的代理方法,这执行代理方法将返回Promise对象
  • prom.call(ctx) 把上下文判定到方法中,以便保证所有中间件中的this均指向同一上下文对象
7 回复

(削除) 先发个初版,后续继续更新 (削除ここまで)

koa或者koa2有办法支持tcp服务么?

@hwoarangzk

我记得源码中包含了不少Http相关的东西,如果是用在纯TCP服务中不大合适

可以像我那样自己去实现Koa的相似功能

回到顶部

AltStyle によって変換されたページ (->オリジナル) /