资讯专栏INFORMATION COLUMN

koa原理浅析

cnio / 3066人阅读

摘要:应用级别顶层处理在上面中间件执行时看到,会自动帮我们捕获错误并处理,如下捕获错误在中处理我们看发现它事实上是出发监听的事件假如我们没有定义回调怎么办呢,也为我们定义了默认的错误处理函数方法做了判断全文完

koa原理浅析
选取的版本为koa2
原文链接

koa的源码由四个文件组成

application.js    koa的骨架
context.js        ctx的原型
request.js        request的原型
response.js       response的原型
基本用法
const Koa = require("koa");
const app = new Koa();

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

app.listen(3000);
初始服务器

利用http模块创建服务器

const app = http.createServer((req, res) => {
    ...
})  
app.listen(3000)

事实上koa把这些包在了其listen方法中

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

显然this.callback()返回的是一个形如下面的函数

(req, res) => {}
上下文ctx

callback方法如下

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

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

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

    return handleRequest;
  }

ctx在koa中事实上是一个包装了request和response的对象,从createContext中可以看到起继承自context

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

可以看到ctx.request继承自request,ctx.response继承自response,查看response和request可以看到里面大都是set和get方法(获取query,设置header)等等。并且ctx代理了ctx.request和ctx.response的方法,在源码中可以看到

delegate(proto, "response")
  .method("attachment")
  .method("redirect")
  .method("remove")
  .method("vary")
  .method("set")
  .method("append")
  .method("flushHeaders")
  .access("status")
  .access("message")
  .access("body")
  .access("length")
  .access("type")
  .access("lastModified")
  .access("etag")
  .getter("headerSent")
  .getter("writable");

/**
 * Request delegation.
 */

delegate(proto, "request")
  .method("acceptsLanguages")
  .method("acceptsEncodings")
  .method("acceptsCharsets")
  .method("accepts")
  .method("get")
  .method("is")
  .access("querystring")
  .access("idempotent")
  .access("socket")
  .access("search")
  .access("method")
  .access("query")
  .access("path")
  .access("url")
  .getter("origin")
  .getter("href")
  .getter("subdomains")
  .getter("protocol")
  .getter("host")
  .getter("hostname")
  .getter("URL")
  .getter("header")
  .getter("headers")
  .getter("secure")
  .getter("stale")
  .getter("fresh")
  .getter("ips")
  .getter("ip");

所以我们可以直接这么写

ctx.url

等价于

ctx.request.url
中间件

我们再看一下callback函数,观察发现compose模块十分的神奇,我暂且把它称为是一个迭代器,它实现了中间件的顺序执行

const fn = compose(this.middleware);

打印fn如下

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

最初接触koa的时候我疑惑为什么我写了

ctx.body = "hello world"

并没有ctx.response.end()之类的方法,事实上koa已经帮我们做了处理,在handleRequest方法中

const handleResponse = () => respond(ctx);

// fnMiddleware即为上面compose之后的fn
fnMiddleware(ctx).then(handleResponse).catch(onerror)

fnMiddleware返回的是一个promise,在中间件逻辑完成后在respond函数中最终去处理ctx.body

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  const res = ctx.res;
  if (!ctx.writable) return;

  let body = ctx.body;
  const code = ctx.status;

  // ignore body
  if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
  }

  if ("HEAD" == ctx.method) {
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }

  // status body
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = "text";
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }

  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ("string" == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}
错误处理

(非首部)中间件层处理(我瞎起的)

对于每个中间件可能发生的错误,可以直接在该中间件捕获

app.use((ctx, next) => {

    try {
        ...        
    } catch(err) {
        ...
    }

})

(首部)中间件层处理

事实上,我们只要在第一个中间件添加try... catch... ,整个中间件组的错误都是可以捕获的到的。

(应用级别)顶层处理

app.on("error", (err) = {})

在上面中间件执行时看到,koa会自动帮我们捕获错误并处理,如下

      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        //  捕获错误
        return Promise.reject(err)
      }


//  在ctx.onerror中处理
const onerror = err => ctx.onerror(err);
fnMiddleware(ctx).then(handleResponse).catch(onerror)

我们看ctx.onerror发现它事实上是出发app监听的error事件

  onerror(err) {


// delegate
    this.app.emit("error", err, this);

假如我们没有定义error回调怎么办呢,koa也为我们定义了默认的错误处理函数

callback方法做了判断

  callback() {

    ...

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

    ...
  }

全文完

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

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

相关文章

  • koa2 总体流程原理浅析(一) 之 koa 启动服务器解析

    摘要:启动流程主要的启动流程就是下面的步引入包实例化编写中间件监听服务器引入包引入包其实就是引入的一个继承于原生的类的类其中就包含了等原型方法实例化执行,将等对象封装在实例中编写中间件首先判断的类型,不是方法直接抛错是生成器函数的话用封装是函数 启动流程 koa 主要的启动流程就是下面的 4 步:引入 koa 包 => 实例化 koa => 编写中间件 => 监听服务器 const koa ...

    fsmStudy 评论0 收藏0
  • koa2 总体流程原理浅析(二) 之 中间件原理

    摘要:任何一层报错,都能用捕获总结是一个非常轻量级的框架,只实现了中间件处理流程和对对象的封装。其他的功能都由外部中间件提供。 koa 的中间件机制巧妙的运用了闭包和 async await 的特点,形成了一个洋葱式的流程,和 JS 的事件流 (捕获 -> target -> 冒泡) 相似 handleRequest(ctx, fnMiddleware) { const res ...

    zhoutk 评论0 收藏0
  • koa-router 源码浅析

    摘要:代码结构执行流程上面两张图主要将的整体代码结构和大概的执行流程画了出来,画的不够具体。那下面主要讲中的几处的关键代码解读一下。全局的路由参数处理的中间件组成的对象。 代码结构 showImg(https://segmentfault.com/img/remote/1460000007468236?w=1425&h=1772); 执行流程 showImg(https://segmentf...

    SillyMonkey 评论0 收藏0
  • 浅析koa的洋葱模型实现

    摘要:前言被认为是第二代,它最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型。这段代码就很巧妙的实现了两点将一路传下去给中间件将中的下一个中间件作为未来的返回值这两点也是洋葱模型实现的核心。 前言 koa被认为是第二代node web framework,它最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型。koa和koa2中间件的思路是一样的,但是实现方式有所区别,koa2在...

    dabai 评论0 收藏0
  • generator探幽(1)--koa中间件机制浅析

    摘要:当运行到时,不会暂停,而是直接跳进函数执行函数内的代码。由于函数中没有,因此会一直执行完函数中的代码,并返回至函数中执行后面的代码。 本系列旨在通过对co,koa等库源码的研究,进而理解generator在异步编程中的重大作用(ps:所有代码请在node --harmony或者iojs环境中运行) koa中间件的形式 相信用过koa的小伙伴一定很熟悉下面这段代码 var app ...

    Jensen 评论0 收藏0

发表评论

0条评论

cnio

|高级讲师

TA的文章

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