Question List
- koa原理,为什么要用koa ( express 和 koa 对比)
- 使用过的
koa中间件
- koa中
response.send
、response.rounded
、response.json
发生了什么事,浏览器为什么能识别到它是一个json结构或是html
koa-bodyparser
怎么来解析request
Answer
一、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 的 req
、res
对象传递给 app.handle
函数,使得handle内部具有req
、res
对象的控制权。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.send
、response.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
类型,将获取到请求的内容流进行解析。主要做了以下几点的实现:
- 处理不同类型的请求体,如:
text
、json
、urlencoded
,对应主体的格式不同 - 处理不同的编码:
utf8
,gbk
等; - 处理不同的压缩类型:
gzip
、deflate
、identity
等 - 其他边界、异常的处理
简单使用,在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
上,以及request
和 response
。对于将 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