记一次koa2源码的探索经历

1.本文解决两个问题,koa启动的时候需要初始化哪些东西和做了哪些事情?

2.一次完整的http请求,对于koa来说是怎么处理的?

3.我们先来回顾一下,我们在使用koa时所使用的一些代码

const koa = require('koa');//加载koa框架
const app = new koa(); //创建一个实例
app.use(fn);  //在use方法里使用你需要使用的方法 它会被挂载到你的中间件上去
//加载路由
router(app);
//监听端口号,进行调用
app.listen(8000, () => {
    logger.info('webServer 服务开启');
});

//我创建的路由 const router = (app) => {  app.use(xmlBody()); //解析表单提交时post的中间件   app.use(koaBody({ multipart: true })); //设为true 表示可以上传文件 app.use(errHandler);//捕捉错误的中间件 app.use(route.post(url,fn); }

//定义的一个fn方法

const fn = async (ctx)=>{

//实现你的业务逻辑

}

4.我们再来看下,koa2的源码结构


我们可以看到它有四个js和众多的依赖模块。那么我们再来解释下,app启动时的调用这些函数时到底做了什么。

首先在new koa() 时,实际上是触发了application.js里面的构造函数。然后我们通过app.use(fn),放入我们的方法来告诉koa,我们进行了注册,再通过listen方法来监听我们端口并开启了服务。

 我们再看下application.js的构造函数,你可以看到实际上它创建了一个中间件和创建了三个对象

  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);  //请求进来时的request方法
    this.response = Object.create(response); //请求返回时的response方法
  }

2.紧接着,我们来解答,这个中间件是干嘛的和我们的app.use 方法有何关联

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
  }

你会发现use方法就干了一件事情,将我们传入的方法放到了middleware数组里,仅此而已。

3.接下来我们再来看listen方法

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

你会发现它调用了http模块的createServer方法,然后监听。和以前原生创建的web服务的方法一模一样。那么我们再来关心下callback里面的内容。

  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;
  }

这个时候,你惊奇的发现中间件又出现了,然后在handleRequest里被调用。那么再重点看一下koa-compose.js里面的这个compose方法。

'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

function compose (middleware) {
  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!')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    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
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

这段代码的主要是用来合并中间件的,具体的意义是,我在调用A方法的时候,在A的中间我又想调用B方法了。这段代码就是用来解决这个问题的。

从这段代码中,我们能知道我们使用app.use()方法 实际上是一个方法层层嵌套调用的过程,是符合上述场景的。

举个例子:

const one = (ctx, next) => {
  console.log('>> one');
  next();
  console.log('<< one');
}

const two = (ctx, next) => {
  console.log('>> two');
  next(); 
  console.log('<< two');
}

const three = (ctx, next) => {
  console.log('>> three');
  next();
  console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);
运行结果如下:
>> one
>> two
>> three
<< three
<< two
<< one

这是一个洋葱圈模型,层层调用,直到最后一个函数没有next函数调用为止,然后逐层返回结果。实际上那个next函数,就是源码在递归调用的

      function next () {
          return dispatch(i + 1)
        })

如果你还不懂,那么把compose方法复制过去,直接运行跟代码即可。如果你不想再往下调用了,那么在你的函数体里面就不用调用next方法即可。


总结:

实际上说了这么多其实最核心的还是koa2中间件原理的实现,如果有对http/https不熟悉的建议看下nodejs文档和基础知识,对于其它源码还是比较容易懂的。

掌握了中间件,我们可以编写一些过滤器,如报错的中间件放在最外层对我们的方法进行报错处理。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值