koa-body是一个解析请求体的插件,它能帮我们处理post方式的json请求体、form表单请求体、文件流请求体等形式。业务应用之所以需要这个中间件,是因为原生的NodeJS或Koa应用它们仅能解析get请求参数,无法解析post请求体参数。
流程概览
koa-body解析请求体主要分为如下流程:
- 从request流中
读取
请求体数据 - 对不同格式的请求体使用
对应的解析器parse
(json请求体、form表单请求体、普通文本请求体、分片请求) - 将parse完的请求数据挂载到
ctx.req.body
上,供业务层使用
接下来我们结合源码一一讲解下这3个流程,koa-body是如何做的
1. 从request流中读取请求体数据
从request中读取请求体数据是借用raw-body这个插件实现的,入口在getRawBody方法中,主要实现在readStream中。我们用如下伪代码诠释它主要做的工作
function getRawBody(req, options) {
return new Promise((resolve, reject) => {
let buffer = ''
req.on('data', (chunk) => {
buffer += decoder.write(chunk);
});
req.on('end', () => {
resolve(buffer);
})
})
}
如上可以看到实际流程较简单,就是监听request对象的data事件,将接收到的流分片数据拼接,在接收完成触发end事件是,将拼接完的数据resolve返回。
但这里我们注意到在拼接流分片数据前,数据会经过decoder
处理,这里decoder其实就是utf-8编码解码处理,因为不同系统数据编码方式是不一样的,用utf-8抹平操作系统差异解码出人类可读的数据。
这里可能某些对NodeJS原生请求流处理不太熟悉的同学会问,为什么req上监听data事件就能拿到请求体的流数据呢?
好问题,其实这里的req(ctx.req
)就是NodeJS原生http模块的request对象,koa框架只是对应该对象做了一些扩展,而http模块的request对象继承于原生Stream
对象,实现了Stream的一切功能
而
request
又是一个可读流
,所以我们自然可以操作这个流去读取数据。
2. 对不同格式的请求体使用对应的解析器parse(json请求体、form表单请求体、普通文本请求体、分片请求)
从中间件的入口
我们可以看到,会通过koa的is工具类判断当前请求的类型,然后if/else if匹配对应类型格式,使用相应的解析器对应处理。
这里我们拿最通用的json解析器来看:
解析器是由co-body提供的
而json解析则位于json.js文件中,用伪代码解释其整体运作如下
async function jsonParser(req, opts) {
// 前面说过的通过raw-body读取到的请求流数据
const str = await raw(req, opts);
return JSON.parse(str);
}
其实就是用我们熟知的json反序列化工具JSON.parse
将请求体处理成json object
,这样就方便后续的业务层去使用啦~
至此,一个request请求体的解析器解读完成,接下来我们看看koa-body是将parse完的请求数据放到了哪
3. 将parse完的请求数据挂载到ctx.req.body上,供业务层使用
可以看到如果opts.patchKoa为true的话,我们的json请求体则会被挂载到ctx.req.body上,ctx.req则是贯穿当前koa单次请求的全局对象,这样再适合不过了,既可以让业务层全局读取,又不会污染到其他请求,因为每次请求对应的req对象都是唯一的。
细心的小伙伴会看到前面说过了会有如果这个opts.patchKoa为true
前提,继续翻阅入口函数,我们发现
koa-body对于用户的options配置做了一些列的初始化,而pathKoa默认是
true
的,所以我们不用做任何处理,koa-body就默认帮我们把解析完的请求体挂载到了ctx.req上!