资讯专栏INFORMATION COLUMN

Koa源码分析

Render / 604人阅读

摘要:上篇文章写了如何阅读的源码粗略的过了一下的源码但是作为一个没有得出一个具体的结论中间件的运行原理也不清楚这里我们再仔细的过一遍的源码跟着例子过一遍首先还是先过一遍例子起一个服务来一个作为模块的再封装我们还是慢慢来挖掘它是如何封装的吧无关的代

上篇文章写了如何阅读Koa的源码, 粗略的过了一下Koa的源码, 但是作为一个没有得出一个具体的结论, 中间件的运行原理也不清楚, 这里我们再仔细的过一遍Koa的源码.

跟着例子过一遍

首先还是先过一遍例子

const Koa = require("koa");
const app = new Koa();

app.use(async ctx => {
  ctx.body = "Hello World";
});

app.listen(3000);

起一个web服务, 来一个Hello World, 作为http模块的再封装, 我们还是慢慢来挖掘它是如何封装的吧(无关的代码我都会删掉).

首先是listen:

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

http模块我们都知道 无非是http.createServer(fn).listen(port), 其中fn带着req, res. 根据上面的封装我们可以肯定this.callback肯定是带着请求以及进行响应了. 那么再来看看this.callback吧.

  callback() {
    const fn = compose(this.middleware);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

果然callback返回的函数是带着req, res的, 那我继续往下走看handleRequest究竟经历了什么, ctx大佬出现了, 我们在用koa的时候所有请求响应都是挂在ctx上的, 看起来ctx是通过createContext创建的, 那就继续看createContext吧:

  createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || "";
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
  }

createContext比较简单, 就是把各种有用的没用的变量挂到context上, 代码也很简单, 但是因为涉及到request和response我们需要简单看一下request.js和response.js:

module.exports = {
  get header() {
    return this.req.headers;
  },
//..more items
}

都是很简单获取变量没啥好说的, 那么回到前面callback部分, ctx创建好了然后调用并返回了this.handleReques, 没啥好说的, 继续看呗:

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

这一部分略微复杂一点, 由上面看出来fnMiddleware 是我们取出来的中间件, 然后我们把ctx传到中间件里执行, 跟我们的通常用法有点像了. 到这一步重点来了: 中间件

中间件

在探究中间件的原理之前, 不妨先来看看中间件是怎么用的, 来个简单的例子:

const Koa = require("koa")
const app = new Koa()

app.use(async function m1 (ctx, nex) {
   console.log("m1")
   await next()
   console.log("m2 end")
})

app.use(async function m2 (ctx, nex) {
  console.log("m2")
  await next()
  console.log("m2 end")
})

app.use(async function m3 (ctx, nex) {
  console.log("m3")
  ctx.body = "Hello World"
})

上面的结果很明确了, 但是我们不妨来可视化一下:
m1: 输出m1
await1: m1你先暂停一下让m2先走
m1: ...
m2: 输出m2
await2: m2你也停一下让m3先走
m2: ...(委屈)
m3: 输出m3, 上面的注意啦要远路返回了
m2: 输出m2 end m1注意了我要返回啦
m1: 输出m1 end

respond: ctx.body是Hello world呢 就糊弄一下用户返回吧

看到没, ctx.body不代表立即响应, 仅仅是一个我们后面会用到的变量, 也就是说我们的ctx过了一遍所有的中间件然后才会做出响应. 这里不提await神奇的暂停效果, 我们就需要可以这么用就行了. 那么我们这个中间件是怎么实现的呢, 来看compose.js:

function compose (middleware) {

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

  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      if (!fn) return Promise.resolve()
      return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
    }
  }
}

看过我前一篇的可以知道这里其实就是一个递归. 但是跟connect的递归不一样这里是Promise, 我们都知道await 跟Promise搭配味道更佳嘛. 重点是这个next, 我们调用了await next之后, 程序非得等这个Promise执行完不可, 我们来简化一下中间件的模型:

Promise.resolve(async m1 () {
  console.log(m1)
  await Promise.resolve(async m2 () {
    console.log(m2)
    await Promise.resolve(async m3 () {
      console.log(m3)
      ctx.body = "xxx"
     })
     console.log(m2 end)
  })
  console.log(m1 end)
})

是不是这样一下就清楚了, 作为应用层的东西, 我们不需要去考虑async/await究竟是怎么实现的, 只需要了解它实现了什么样的效果.

还是得佩服tj大神. 有问题可以互相交流哈.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92621.html

相关文章

  • 中间件执行模块koa-Compose源码分析

    摘要:原文博客地址,欢迎学习交流点击预览读了下的源码,写的相当的精简,遇到处理中间件执行的模块决定学习一下这个模块的源码。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。 原文博客地址,欢迎学习交流:点击预览 读了下Koa的源码,写的相当的精简,遇到处理中间件执行的模块koa-Compose,决定学习一下这个模块的源码。 阅读本文可以学到: Koa中间件的加载...

    imtianx 评论0 收藏0
  • Koa源码阅读笔记(4) -- ctx对象

    摘要:本笔记共四篇源码阅读笔记源码阅读笔记源码阅读笔记服务器启动与请求处理源码阅读笔记对象起因前两天终于把自己一直想读的源代码读了一遍。首先放上关键的源代码在上一篇源码阅读笔记服务器启动与请求处理中,我们已经分析了的作用。 本笔记共四篇Koa源码阅读笔记(1) -- coKoa源码阅读笔记(2) -- composeKoa源码阅读笔记(3) -- 服务器の启动与请求处理Koa源码阅读笔记(4...

    ityouknow 评论0 收藏0
  • koa2 一网打尽(基本使用,洋葱圈,中间件机制和模拟,源码分析(工程,核心模块,特殊处理),核心点

    摘要:洋葱圈处理模型。基于的灵活强大的中间件机制。参考官网提供的基本,不在赘述部分实现,参考源码分析常用服务端口监听返回适用于方法的回调函数来处理请求。 本文 github 地址: https://github.com/HCThink/h-blog/blob/master/source/koa2/readme.md github 首页(star+watch,一手动态直达): https:...

    william 评论0 收藏0
  • koa2源码分析

    摘要:下面基于构造函数入口做进一步分析。创建一个空数组存放放,洋葱流程的真相下面会分析法到决定忽略的子域名数量,默认为处理环境变量实例上挂载说明上面是构造函数入口启动入口如下借用原生,添加。 引言 最近在写koa2相关例子,顺便看了下koa2的源码,下面是一些个人理解。 koa1核心基于generator,但是严重依赖co的包装。koa2完全不需要,基于async(其实质是generator...

    ningwang 评论0 收藏0
  • koa源码分析系列(一)

    摘要:很明显是一个构造函数。默认为根据原生的对象生成一个对象回调函数处理服务器响应可以看到,方法返回的函数就是方法所需要的回调函数。 koa 是什么这里不介绍了,这里通过一个小例子结合源码讲一讲它的实现。 koa 源码结构 通过 npm 安装 koa(v2.2.0) 后,代码都在 lib 文件夹内,包括 4 个文件,application.js, context.js, request.js...

    Atom 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<