精华 Koa中间件功能源码阅读
发布于 5 年前 作者 CoderIvan 4483 次浏览 来自 分享

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的相似功能

回到顶部