koa2源码分析
发布于 7 年前 作者 hunnble 3928 次浏览 来自 分享

本文是对Node.js框架koa部分源码的分析,如有错误欢迎指正。

从构造函数开始

constructor() {
  super();
  this.proxy = false;
  this.middleware = [];
  this.subdomainOffset = 2;
  this.env = process.env.NODE_ENV || 'development';
  this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
}

koa的Application类继承了events模块,在构造函数中做了这些事情:

  • 默认不设置代理
  • middleware是中间件数组
  • 子域名偏移量默认为2,也就是默认忽略数量为2
  • 环境变量的处理
  • 挂载context、request、response

服务器的启动

listen(...args) {
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

调用Node.js的http模块来创建一个服务器,具体的handle是使用callback方法的返回值。最后将listen函数的参数透传到创建好的服务器实例的listen方法中完成服务器的启动。

请求的处理

下面来分析刚刚说过的callback方法,我们已经知道,它的返回值是一个交付给http模块的server实例的请求处理(request handler)函数,下面来看实现

callback() {
  const fn = compose(this.middleware);

  if (!this.listeners('error').length) this.on('error', this.onerror);

  const handleRequest = (req, res) => {
    res.statusCode = 404;
    const ctx = this.createContext(req, res);
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fn(ctx).then(handleResponse).catch(onerror);
  };

  return handleRequest;
}

使用koa-compose模块组合中间件 -> 中间件机制

koa的中间件遵循流水线模式,多个中间件依次输入,之后倒序执行,也就是官方的那张洋葱图。

执行顺序

function compose (middleware) {
  // 这里对middleware的类型和每一个中间件的类型做了判断
  // 话说js类型系统是一大黑点啊
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  return function (context, next) {
    let index = -1
    // 递归调用dispatch,每一次在调用async next()时都resolve一个中间件,最后倒序执行每一个函数体后面的代码。
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      // 返回Promise实例,使后面的逻辑可以执行
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

handleRequest

  • 通过createContext方法得到ctx,也就是context对象,它把request和response的一些方法代理到自己身上
  • onerror: 可以监听全局错误,包含了一些基本的异常处理,具体的代码是在源码的context.js中
  • handleResponse处理response
  • 用on-finished模块+刚才说的onerror来捕获和处理异常

use方法

koa中,使用中间件非常方便,只需要写类似的代码

app.use(async (ctx, next) => {
  const start = new Date();
  await next();
  const ms = new Date() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

use方法接受一个函数参数, 返回app实例自身,所以可以链式地写一长串。下面分析一下use方法的源码

use(fn) {
  // 又见类型检测...
  if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
  // 这里我删掉一段判断Generator函数的代码,众所周知,koa1是基于generator和co的,而koa2换成了async await的语法糖
  this.middleware.push(fn);
  return this;
}

哈,其实就是简单地往middleware数组push了中间件而已,很简单。

2 回复

话说以前发现,把 compose 方法的:

Promise.resolve(fn(context, function next () {
	return dispatch(i + 1)
})

改成

Promise.resolve().then(fn(context, function next () {
	return dispatch(i + 1)
})

中间件数量多的时候就不会栈溢出了…

http://www.cnblogs.com/Rwing/p/koa2-source-code-overview.html 我也曾经写过一个。。。但是没有坚持后续……

回到顶部