Koa 与 Express 对比

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源码构成中间件),是一个洋葱圈模型。

Koa 与 Express 对比

Koa 与 Express 对比

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

以上是 Koa 与 Express 对比 的全部内容, 来源链接: utcz.com/p/232786.html

回到顶部