Koa 与 Express 对比

发布于 2020-11-05 11:19:09 字数 10008 浏览 1777 评论 0

Question List

  • koa 原理,为什么要用 koa ( express 和 koa 对比)
  • 使用过的 koa 中间件
  • koa 中 response.sendresponse.roundedresponse.json 发生了什么事,浏览器为什么能识别到它是一个 json 结构或是 html
  • koa-bodyparser 怎么来解析 request

一、Express 框架

Express 是一个轻量级的 Web Framework ,自带 Router、路由规则等,早期版本的 Express 还有 bodyParser ,后期剥离为独立模块作为中间件管理。其中间件模型是基于 callback 回调 实现。

源码理解 app.use(middleware())、router.handle、next

中间件 middlewares 是较多 Web 框架的核心概念,可以根据不同的业务场景,集成到框架中,进而增强框架的服务能力,而框架也是需要提供一套机制来保证中间件有序的执行。

在 Express 中,我们是通过 app.use(middleware()) 的方式注册中间件,见 using-middleware 文档 。use 的顺序和规则 express 都做了控制。我们可以看一下源码进行分析。

express.js

Express 服务实例将 Node.js 的 reqres 对象传递给 app.handle 函数,使得 handle 内部具有 reqres 对象的控制权。handle 函数还有一个叫 next 的参数, next 在中间件控制权起到了十分重要的作用。

代码:
https://github.com/expressjs/express/blob/dc538f6e810bd462c98ee7e6aae24c64d4b1da93/lib/express.js#L37

application.js

app.handle 中,如果是路由的情况,还会将控制权转给 router.handle ,并传入 res、req、callback, app.use 方法作为 路由的 Router#use() 代理方法添加中间件到路由。

代码:
https://github.com/expressjs/express/blob/dc538f6e810bd462c98ee7e6aae24c64d4b1da93/lib/application.js#L158

router/index.js

Router#use() 中使用了 layer 来存放中间件,类似一个等待执行的中间件堆叠层。

router.handle 方法三个参数为 res、req、out,第三个参数变化了名称为 out ,意思可以理解为这是要原路返回出去的。

https://github.com/expressjs/express/blob/dc538f6e810bd462c98ee7e6aae24c64d4b1da93/lib/router/index.js#L136

这里关键部分在于内部函数 next ,next 会去查找匹配 layer 对叠层 ,如果匹配到,将会通过 proto.process_params 来处理,将参数传递给 layer 层并执行,最后 layer.handle_request 执行的就是路由的 handle

function next(err) {
    var layerError = err === 'route'
      ? null
      : err;
   ……
 // find next matching layer
    var layer;
    var match;
    var route;

    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;
      ……
    }
}

整个过程中, next 起到了关键作用——所有的中间件都要执行 next,从而把当前的控制权以回调的方式往下面传递。

Express 中 response.sendresponse.json 发生了什么事,浏览器为什么能识别到它是一个 json 结构或是 html

response.json 本质上也是调用 response.send 方法,所以只需要分析一下 response.send 的源码即可。

res.send 中通过判断 chunk (body) 的类型,以及 Content-Type 的值,来动态 设置 Content-Type 类型,使得浏览器知道响应的内容是什么类型数据。Express 请求响应 Content-Type 类型常见有:

res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png');

Express 中间件 body-parser 如何解析 request

从源码可以看到, body-parser 通过根据请求报文主体的压缩格式 Content-Encoding 类型,将获取到 请求的内容流 进行解析。主要做了以下几点的实现:

  • 处理不同类型的请求体,如: textjsonurlencoded ,对应主体的格式不同
  • 处理不同的编码: utf8gbk 等;
  • 处理不同的压缩类型: gzipdeflateidentity
  • 其他边界、异常的处理

简单使用,在 Express 中,通过设置请求为 json 格式

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

二、Koa 框架

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

Koa1.0 是基于 co 实现,通过 Generator/yield 来控制异步(详细了解 co 模块tj 说:co 是 async/await 的一块垫脚石 )。随后 Koa2.0 改用 ES7 中的 async/await 来配合 Promise 实现异步控制。

Context

上下文对象 ctx 是由 createContext 创建的。主要把一些属性和变量挂载到 context 上,以及 requestresponse 。对于将 ctx 添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的 require()),而更多依赖于 ctx ,这可以被认为是一种反模式。

洋葱圈模型 & next()

当一个中间件调用 next() ,则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈展开并且每个中间件恢复执行其上游行为。

Koa 中间件构成实现模块是 koa-compose (application 源码 构成中间件 ),是一个洋葱圈模型。

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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

从源码可以看出, compose 对中间件进行了递归的操作,最终形成了一个中间件自执行链(只要第一个中间件执行了,随后的中间件都会依次被执行),这与 koa1.0 版本基于 co 实现一个目的, koa1.0 利用 Thunk 函数对 generator yield 异步操作封装成达到自执行目的。 Koa2 之后,就改用 async/await 配合 promise 来实现了,上边代码就是中间件自执行操作的核心。

每个中间件都被封装成了一个 Promise 对象 。(这也是可以猜到的,因为 await 配合 Promise 才是最佳的。)

如下例子:

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


app.use(async function m1(ctx, next) {
    console.log('m1');
    await next(); // 暂停进入下一个中间件
    console.log('m1 end');
    const rt = ctx.response.get('X-Response-Time');
    console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});


app.use(async function m2(ctx, next) {
    const start = Date.now();
    console.log('m2');
    await next(); // 暂停进入下一个中间件
    console.log('m2 end');
    const ms = Date.now() - start;
    ctx.set('X-Response-Time', `${ms}ms`);
});

app.use(async function m3(ctx, next) {
    console.log('m3');
    ctx.body = 'Hello World!';
});

app.listen(3000);

输出结果:


// 请求开始
m1
// m1 中 await next()进入暂停,进入下一个中间件 m2
m2
//  m2 中 await next()进入暂停,进入下一个中间件 m3
m3
//  洋葱模型,逆向回去,先 m2 的
m2 end
//  洋葱模型,逆向回去,m2 执行完毕后进行上游 m1 的
m1 end
GET / - 2ms
// 响应结束

异常处理

Koa 还提供了异常处理的解决方式,统一的异常处理源码见 ctx.onerror ,我们可以使用 app.on('error',()=>{}) 来统一错误处理。


参考资料

https://www.zhihu.com/question/38879363
https://www.imooc.com/article/22994
https://www.cnblogs.com/chyingp/p/nodejs-learning-express-body-parser.html
https://juejin.im/post/5a62bab4f265da3e58596f40

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文