【Node.js】 bodyparser实现原理解析
<p><img src="https://img2018.cnblogs.com/blog/1060770/201908/1060770-20190818221933982-173721055.png" alt=""></p><h2><span style="font-family: 楷体; font-size: 18px">为什么我们需要body-parser</span></h2>
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="dc3br-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="dc3br-0-0">
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="dc3br-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="dc3br-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="dc3br-0-0">也许你第一次和bodyparser相遇是在使用Koa框架的时候<span data-offset-key="dc3br-0-1">。当我们尝试从一个浏览器发来的POST请求中取得请求报文实体的时候,这个时候,我们想,这个从<strong>Koa</strong>自带的<strong>ctx.body</strong>里面取出来就可以了嘛!</span></span></div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="dc3br-0-0"> </div>
</div>
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="4m99n-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="4m99n-0-0"><strong><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="4m99n-0-0">唉!等等,但<span data-offset-key="4m99n-0-1">根据Koa文档,ctx.body等同于ctx.res.body,所以从ctx.body取出来的是空的响应报文,而不是请求报文的实体哦</span></span></strong></div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="4m99n-0-0"> </div>
</div>
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="38jvt-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="38jvt-0-0">于是这时候又打算从Node文档里找找request对象有没有可以提供查询请求报文的属性,结<strong>果自然是Node文档自然会告诉你结果</strong>——</span></div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"> <img src="https://img2018.cnblogs.com/blog/1060770/201908/1060770-20190818223555706-456336764.jpg" alt="">
<p> </p>
</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"> </div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="38jvt-0-0">所以,这个时候我们需要的是——</span></div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="38jvt-0-0"><img src="https://img2018.cnblogs.com/blog/1060770/201908/1060770-20190818223602987-915381782.gif" alt=""></span></span>
<p> </p>
</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"> </div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0">
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="4d2rt-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="4d2rt-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="4d2rt-0-0">bodyparser是一类处理request的body的中间件函数,例如Koa-bodyparser就是和Koa框架搭配使用的中间件,帮助没有内置处理该功能的Koa框架提供解析request.body的方法,通过app.use加载Koa-bodyparser后,在Koa中就可以通过ctx.request.body访问到请求报文的报文实体啦!</span></div>
</div>
<h2><span style="font-size: 18px; font-family: "Microsoft YaHei"" data-offset-key="41b9p-0-0">body-parser代码逻辑</span></h2>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="9nvvr-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="9nvvr-0-0">无论是Node的哪一款body-parser,其原理都是类似的今天我们就编写一个getRequestBody的函数,解析出request.body,以尽管中窥豹之理。</span></div>
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="evj8i-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="evj8i-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="evj8i-0-0"> </span></div>
</div>
<div class="Editable-unstyled" data-block="true" data-editor="1gbn7" data-offset-key="1tdr8-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="1tdr8-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="1tdr8-0-0">要编写body-parser的代码,首先要了解两个方面的逻辑:请求相关事件和数据处理流程</span></div>
</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="5dquj-0-0"><span style="font-size: 18px; font-family: 楷体"><strong><span data-offset-key="5dquj-0-0">请求相关事件</span></strong></span></div>
<ul class="public-DraftStyleDefault-ul" data-offset-key="ec1j7-0-0">
<li class="Editable-styled public-DraftStyleDefault-unorderedListItem public-DraftStyleDefault-reset public-DraftStyleDefault-depth0 public-DraftStyleDefault-listLTR" data-block="true" data-editor="1gbn7" data-offset-key="ec1j7-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="ec1j7-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="ec1j7-0-0">data事件:当request接收到数据的时候触发,在数据传输结束前可能会触发多次,在事件回调里可以接收到Buffer类型的数据参数,我们可以将Buffer数据对象收集到数组里</span></div>
</li>
<li class="Editable-styled public-DraftStyleDefault-unorderedListItem public-DraftStyleDefault-depth0 public-DraftStyleDefault-listLTR" data-block="true" data-editor="1gbn7" data-offset-key="ap3iu-0-0">
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="ap3iu-0-0"><span style="font-size: 16px; font-family: "Microsoft YaHei"" data-offset-key="ap3iu-0-0">end事件:请求数据接收结束时候触发,不提供参数,我们可以在这里将之前收集的Buffer数组集中处理,最后输出将request.body输出。</span></div>
</li>
</ul>
<p><span style="font-family: 楷体"><strong><span style="font-size: 18px">数据处理流程</span></strong></span></p>
<ol>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">在request的data事件触发时候,收集Buffer对象,将其放到一个命名为chunks的数组中</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">在request的end事件触发时,通过Buffer.concat(chunks)将Buffer数组整合成单一的大的Buffer对象</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">解析请求首部的Content-Encoding,根据类型,如gzip,deflate等调用相应的解压缩函数如Zlib.gunzip,将2中得到的Buffer解压,返回的是解压后的Buffer对象</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">解析请求的charset字符编码,根据其类型,如gbk或者utf-8,调用iconv库提供的decode(buffer, charset)方法,根据字符编码将3中的Buffer转换成字符串</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">最后,根据Content-Type,如application/json或'application/x-www-form-urlencoded'对4中得到的字符串做相应的解析处理,得到最后的对象,作为request.body返回</span></li>
</ol>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">下面展示下相关的代码</span></p>
<h3><span style="font-size: 18px; font-family: 楷体">整体代码结构</span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据Content-Encoding判断是否解压,如需则调用相应解压函数</span>
async <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> transformEncode(buffer, encode) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> charset转码</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> transformCharset(buffer, charset) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据content-type做最后的数据格式化</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> formatData(str, contentType) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回Promise</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getRequestBody(req, res) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Promise(async (resolve, reject) =><span style="color: rgba(0, 0, 0, 1)"> {
const chunks </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
req.on(</span>'data', buf =><span style="color: rgba(0, 0, 0, 1)"> {
chunks.push(buf);
})
req.on(</span>'end', async () =><span style="color: rgba(0, 0, 0, 1)"> {
let buffer </span>=<span style="color: rgba(0, 0, 0, 1)"> Buffer.concat(chunks);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取content-encoding</span>
const encode = req.headers['content-encoding'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取content-type</span>
const { type, parameters } =<span style="color: rgba(0, 0, 0, 1)"> contentType.parse(req);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取charset</span>
const charset =<span style="color: rgba(0, 0, 0, 1)"> parameters.charset;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解压缩</span>
buffer =<span style="color: rgba(0, 0, 0, 1)"> await transformEncode(buffer, encode);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换字符编码</span>
const str =<span style="color: rgba(0, 0, 0, 1)"> transformCharset(buffer, charset);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据类型输出不同格式的数据,如字符串或JSON对象</span>
const result =<span style="color: rgba(0, 0, 0, 1)"> formatData(str, type);
resolve(result);
})
}).</span><span style="color: rgba(0, 0, 255, 1)">catch</span>(err => { <span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)"> err; })
}</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step0.Promise<span style="font-family: 楷体">的编程风格</span></span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getRequestBody(req, res) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Promise(async (resolve, reject) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step1.data<span style="font-family: 楷体">事件的处理</span></span></h3>
<div class="cnblogs_code">
<pre>const chunks =<span style="color: rgba(0, 0, 0, 1)"> [];
req.on(</span>'data', buf =><span style="color: rgba(0, 0, 0, 1)"> {
chunks.push(buf);
})</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step2.end<span style="font-family: 楷体">事件的处理</span></span></h3>
<div class="cnblogs_code">
<pre>const contentType = require('content-type'<span style="color: rgba(0, 0, 0, 1)">);
const iconv </span>= require('iconv-lite'<span style="color: rgba(0, 0, 0, 1)">);
req.on(</span>'end', async () =><span style="color: rgba(0, 0, 0, 1)"> {
let buffer </span>=<span style="color: rgba(0, 0, 0, 1)"> Buffer.concat(chunks);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取content-encoding</span>
const encode = req.headers['content-encoding'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取content-type</span>
const { type, parameters } =<span style="color: rgba(0, 0, 0, 1)"> contentType.parse(req);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取charset</span>
const charset =<span style="color: rgba(0, 0, 0, 1)"> parameters.charset;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解压缩</span>
buffer =<span style="color: rgba(0, 0, 0, 1)"> await transformEncode(buffer, encode);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换字符编码</span>
const str =<span style="color: rgba(0, 0, 0, 1)"> transformCharset(buffer, charset);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据类型输出不同格式的数据,如字符串或JSON对象</span>
const result =<span style="color: rgba(0, 0, 0, 1)"> formatData(str, type);
resolve(result);
}</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step3.<span style="font-family: 楷体">根据</span>Content-Encoding<span style="font-family: 楷体">进行解压处理</span></span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">Content-Encoding可分为四种值:gzip,compress,deflate,br,identity</span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">其中</span></p>
<ul>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">identity表示数据保持原样,没有经过压缩</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">compress已经被大多数浏览器废弃,Node没有提供解压的方法</span></li>
</ul>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">所以我们需要处理解压的一共有三种数据类型</span></p>
<ul>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">gzip:采用zlib.gunzip方法解压</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">deflate: 采用zlib.inflate方法解压</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">br:采用zlib.brotliDecompress方法解压</span></li>
</ul>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">(注意!zlib.brotliDecompress方法在Node11.7以上版本才会支持,而且不要看到名字里有compress就误以为它是用来解压compress压缩的数据的,实际上它是用来处理br的)</span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">代码如下,我们对zlib.gunzip等回调类方法通过promisify转成Promise编码风格</span></p>
<p> </p>
<div class="cnblogs_code">
<pre>const promisify =<span style="color: rgba(0, 0, 0, 1)"> util.promisify;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> node 11.7版本以上才支持此方法</span>
const brotliDecompress = zlib.brotliDecompress &&<span style="color: rgba(0, 0, 0, 1)"> promisify(zlib.brotliDecompress);
const gunzip </span>=<span style="color: rgba(0, 0, 0, 1)"> promisify(zlib.gunzip);
const inflate </span>=<span style="color: rgba(0, 0, 0, 1)"> promisify(zlib.inflate);
const querystring </span>= require('querystring'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据Content-Encoding判断是否解压,如需则调用相应解压函数</span>
async <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> transformEncode(buffer, encode) {
let resultBuf </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">debugger</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (encode) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'br'<span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">brotliDecompress) {
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error('Node版本过低! 11.6版本以上才支持brotliDecompress方法'<span style="color: rgba(0, 0, 0, 1)">)
}
resultBuf </span>=<span style="color: rgba(0, 0, 0, 1)"> await brotliDecompress(buffer);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'gzip'<span style="color: rgba(0, 0, 0, 1)">:
resultBuf </span>=<span style="color: rgba(0, 0, 0, 1)"> await gunzip(buffer);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'deflate'<span style="color: rgba(0, 0, 0, 1)">:
resultBuf </span>=<span style="color: rgba(0, 0, 0, 1)"> await inflate(buffer);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
resultBuf </span>=<span style="color: rgba(0, 0, 0, 1)"> buffer;
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> resultBuf;
}</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step4.<span style="font-family: 楷体">根据</span>charset<span style="font-family: 楷体">进行转码处理</span></span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">我们采用iconv-lite对charset进行转码,代码如下</span></p>
<div class="cnblogs_code">
<pre>const iconv = require('iconv-lite'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> charset转码</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> transformCharset(buffer, charset) {
charset </span>= charset || 'UTF-8'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> iconv将Buffer转化为对应charset编码的String</span>
const result =<span style="color: rgba(0, 0, 0, 1)"> iconv.decode(buffer, charset);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p> </p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">来!传送门</span></p>
<p> https://link.zhihu.com/?target=https%3A//www.npmjs.com/package/iconv-lite</p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Step5.<span style="font-family: 楷体">根据</span>contentType<span style="font-family: 楷体">将4中得到的字符串数据进行格式化</span></span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">具体的处理方式分三种情况:</span></p>
<ul>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">对text/plain 保持原样,不做处理,仍然是字符串</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">对application/x-www-form-urlencoded,得到的是类似于key1=val1&key2=val2的数据,通过querystring模块的parse方法转成{ key:val }结构的对象</span></li>
<li><span style="font-size: 16px; font-family: "Microsoft YaHei"">对于application/json,通过JSON.parse(str)一波带走</span></li>
</ul>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">代码如下</span></p>
<p> </p>
<div class="cnblogs_code">
<pre>const querystring = require('querystring'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据content-type做最后的数据格式化</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> formatData(str, contentType) {
let result </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (contentType) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'text/plain'<span style="color: rgba(0, 0, 0, 1)">:
result </span>=<span style="color: rgba(0, 0, 0, 1)"> str;
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'application/json'<span style="color: rgba(0, 0, 0, 1)">:
result </span>=<span style="color: rgba(0, 0, 0, 1)"> JSON.parse(str);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'application/x-www-form-urlencoded'<span style="color: rgba(0, 0, 0, 1)">:
result </span>=<span style="color: rgba(0, 0, 0, 1)"> querystring.parse(str);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p> </p>
<h2><span style="font-size: 18px; font-family: 楷体">测试代码</span></h2>
<p><span style="font-family: 楷体"><strong><span style="font-size: 18px">服务端</span></strong></span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">下面的代码你肯定知道要放在哪里了</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 省略其他代码</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (pathname === '/post'<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用getRequestBody,通过await修饰等待结果返回</span>
const body =<span style="color: rgba(0, 0, 0, 1)"> await getRequestBody(req, res);
console.log(body);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p> </p>
<p><span style="font-size: 18px; font-family: 楷体"><strong>前端采用fetch进行测试</strong></span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">在下面的代码中,我们连续三次发出不同的POST请求,携带不同类型的body数据,看看服务端会输出什么</span></p>
<p> </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> iconv = require('iconv-lite'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> querystring = require('querystring'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> gbkBody =<span style="color: rgba(0, 0, 0, 1)"> {
data: </span>"我是彭湖湾"<span style="color: rgba(0, 0, 0, 1)">,
contentType: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
charset: </span>'gbk'<span style="color: rgba(0, 0, 0, 1)">
};
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转化为JSON数据</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> gbkJson =<span style="color: rgba(0, 0, 0, 1)"> JSON.stringify(gbkBody);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转为gbk编码</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> gbkData = iconv.encode(gbkJson, "gbk"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> isoData = iconv.encode("我是彭湖湾,这句话采用UTF-8格式编码,content-type为text/plain", "UTF-8"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 测试内容类型为application/json和charset=gbk的情况</span>
fetch('/post'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
</span>"Content-Type": 'application/json; charset=gbk'<span style="color: rgba(0, 0, 0, 1)">
},
body: gbkData
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 测试内容类型为application/x-www-form-urlencoded和charset=UTF-8的情况</span>
fetch('/post'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
</span>"Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8'<span style="color: rgba(0, 0, 0, 1)">
},
body: querystring.stringify({
data: </span>"我是彭湖湾"<span style="color: rgba(0, 0, 0, 1)">,
contentType: </span>'application/x-www-form-urlencoded'<span style="color: rgba(0, 0, 0, 1)">,
charset: </span>'UTF-8'<span style="color: rgba(0, 0, 0, 1)">
})
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 测试内容类型为text/plain的情况</span>
fetch('/post'<span style="color: rgba(0, 0, 0, 1)">, {
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
headers: {
</span>"Content-Type": 'text/plain; charset=UTF-8'<span style="color: rgba(0, 0, 0, 1)">
},
body: isoData
});</span></pre>
</div>
<p> </p>
<p><span style="font-family: 楷体"><strong><span style="font-size: 18px">服务端输出结果</span></strong></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
data: </span>'我是彭湖湾'<span style="color: rgba(0, 0, 0, 1)">,
contentType: </span>'application/json'<span style="color: rgba(0, 0, 0, 1)">,
charset: </span>'gbk'<span style="color: rgba(0, 0, 0, 1)">
}
{
data: </span>'我是彭湖湾'<span style="color: rgba(0, 0, 0, 1)">,
contentType: </span>'application/x-www-form-urlencoded'<span style="color: rgba(0, 0, 0, 1)">,
charset: </span>'UTF-8'<span style="color: rgba(0, 0, 0, 1)">
}
我是彭湖湾,这句话采用UTF</span>-8格式编码,content-type为text/plain</pre>
</div>
<p> </p>
<h2><span style="font-size: 18px; font-family: 楷体">问题和后记</span></h2>
<p> </p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Q1.为什么要对charset进行处理</span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">其实本质上来说,charset前端一般都是固定为utf-8的, 甚至在JQuery的AJAX请求中,前端请求charset甚至是不可更改,只能是charset,但是在使用fetch等API的时候,的确是可以更改charset的,这个工作尝试满足一些比较偏僻的更改charset需求。</span></p>
<h3><span style="font-size: 18px; font-family: "Microsoft YaHei"">Q2:为什么要对content-encoding做处理呢?</span></h3>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">一般情况下我们认为,考虑到前端发的AJAX之类的请求的数据量,是不需要做Gzip压缩的。但是向服务器发起请求的不一定只有前端,还可能是Node的客户端。这些Node客户端可能会向Node服务端传送压缩过后的数据流。 例如下面的代码所示</span></p>
<p> </p>
<div class="cnblogs_code">
<pre>const zlib = require('zlib'<span style="color: rgba(0, 0, 0, 1)">);
const request </span>= require('request'<span style="color: rgba(0, 0, 0, 1)">);
const data </span>= zlib.gzipSync(Buffer.from("我是一个被Gzip压缩后的数据"<span style="color: rgba(0, 0, 0, 1)">));
request({
method: </span>'POST'<span style="color: rgba(0, 0, 0, 1)">,
url: </span>'http://127.0.0.1:3000/post'<span style="color: rgba(0, 0, 0, 1)">,
headers: {</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置请求头</span>
"Content-Type": "text/plain"<span style="color: rgba(0, 0, 0, 1)">,
</span>"Content-Encoding": "gzip"<span style="color: rgba(0, 0, 0, 1)">
},
body: data
})</span></pre>
</div>
<p> </p>
<h2><span style="font-size: 16px; font-family: 楷体">项目的github和npm地址</span></h2>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">https://github.com/penghuwan/body-parser-promise</span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">https://www.npmjs.com/package/body-parser-promise</span></p>
<h2><span style="font-size: 18px; font-family: 楷体">参考资料</span></h2>
<p><span style="font-size: 18px; font-family: 楷体">Koa-bodyparser https://github.com/koajs/bodyparser</span></p>
<p> </p>
<h2><span style="font-family: 楷体"><span style="font-family: 楷体"><span style="font-size: 18px">上一篇文章</span></span></span></h2>
<p><span style="font-family: "Microsoft YaHei"; font-size: 16px"><span>如何用JavaScript测网速</span></span></p>
<p><span style="font-size: 16px; font-family: "Microsoft YaHei"">【完】</span></p>
</div>
</div>
</div>
</div>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
我叫彭湖湾,请叫我胖湾<br><br>
来源:https://www.cnblogs.com/penghuwan/p/11374268.html
頁:
[1]