稳步 發表於 2019-8-19 08:17:00

【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: &quot;Microsoft YaHei&quot;" 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">&nbsp;</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: &quot;Microsoft YaHei&quot;" 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">&nbsp;</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: &quot;Microsoft YaHei&quot;" 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">&nbsp;<img src="https://img2018.cnblogs.com/blog/1060770/201908/1060770-20190818223555706-456336764.jpg" alt="">
<p>&nbsp;</p>
</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0">&nbsp;</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" data-offset-key="38jvt-0-0"><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;" data-offset-key="38jvt-0-0"><img src="https://img2018.cnblogs.com/blog/1060770/201908/1060770-20190818223602987-915381782.gif" alt=""></span></span>
<p>&nbsp;</p>
</div>
<div class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr" data-offset-key="38jvt-0-0">&nbsp;</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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" data-offset-key="evj8i-0-0">&nbsp;</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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;" 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: &quot;Microsoft YaHei&quot;">在request的data事件触发时候,收集Buffer对象,将其放到一个命名为chunks的数组中</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">在request的end事件触发时,通过Buffer.concat(chunks)将Buffer数组整合成单一的大的Buffer对象</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">解析请求首部的Content-Encoding,根据类型,如gzip,deflate等调用相应的解压缩函数如Zlib.gunzip,将2中得到的Buffer解压,返回的是解压后的Buffer对象</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">解析请求的charset字符编码,根据其类型,如gbk或者utf-8,调用iconv库提供的decode(buffer, charset)方法,根据字符编码将3中的Buffer转换成字符串</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">最后,根据Content-Type,如application/json或'application/x-www-form-urlencoded'对4中得到的字符串做相应的解析处理,得到最后的对象,作为request.body返回</span></li>
</ol>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">下面展示下相关的代码</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) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      const chunks </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
      req.on(</span>'data', buf =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            chunks.push(buf);
      })
      req.on(</span>'end', async () =&gt;<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 =&gt; { <span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)"> err; })
}</span></pre>
</div>
<p>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">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) =&gt;<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>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">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 =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
chunks.push(buf);
})</span></pre>
</div>
<p>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">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 () =&gt;<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>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">Step3.<span style="font-family: 楷体">根据</span>Content-Encoding<span style="font-family: 楷体">进行解压处理</span></span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">Content-Encoding可分为四种值:gzip,compress,deflate,br,identity</span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">其中</span></p>
<ul>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">identity表示数据保持原样,没有经过压缩</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">compress已经被大多数浏览器废弃,Node没有提供解压的方法</span></li>
</ul>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">所以我们需要处理解压的一共有三种数据类型</span></p>
<ul>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">gzip:采用zlib.gunzip方法解压</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">deflate: 采用zlib.inflate方法解压</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">br:采用zlib.brotliDecompress方法解压</span></li>
</ul>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">(注意!zlib.brotliDecompress方法在Node11.7以上版本才会支持,而且不要看到名字里有compress就误以为它是用来解压compress压缩的数据的,实际上它是用来处理br的)</span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">代码如下,我们对zlib.gunzip等回调类方法通过promisify转成Promise编码风格</span></p>
<p>&nbsp;</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 &amp;&amp;<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>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">Step4.<span style="font-family: 楷体">根据</span>charset<span style="font-family: 楷体">进行转码处理</span></span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">我们采用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>&nbsp;</p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">来!传送门</span></p>
<p>&nbsp;https://link.zhihu.com/?target=https%3A//www.npmjs.com/package/iconv-lite</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">Step5.<span style="font-family: 楷体">根据</span>contentType<span style="font-family: 楷体">将4中得到的字符串数据进行格式化</span></span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">具体的处理方式分三种情况:</span></p>
<ul>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">对text/plain 保持原样,不做处理,仍然是字符串</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">对application/x-www-form-urlencoded,得到的是类似于key1=val1&amp;key2=val2的数据,通过querystring模块的parse方法转成{ key:val }结构的对象</span></li>
<li><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">对于application/json,通过JSON.parse(str)一波带走</span></li>
</ul>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">代码如下</span></p>
<p>&nbsp;</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>&nbsp;</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: &quot;Microsoft YaHei&quot;">下面的代码你肯定知道要放在哪里了</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>&nbsp;</p>
<p><span style="font-size: 18px; font-family: 楷体"><strong>前端采用fetch进行测试</strong></span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">在下面的代码中,我们连续三次发出不同的POST请求,携带不同类型的body数据,看看服务端会输出什么</span></p>
<p>&nbsp;</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>&nbsp;</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>&nbsp;</p>
<h2><span style="font-size: 18px; font-family: 楷体">问题和后记</span></h2>
<p>&nbsp;</p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">Q1.为什么要对charset进行处理</span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">其实本质上来说,charset前端一般都是固定为utf-8的, 甚至在JQuery的AJAX请求中,前端请求charset甚至是不可更改,只能是charset,但是在使用fetch等API的时候,的确是可以更改charset的,这个工作尝试满足一些比较偏僻的更改charset需求。</span></p>
<h3><span style="font-size: 18px; font-family: &quot;Microsoft YaHei&quot;">Q2:为什么要对content-encoding做处理呢?</span></h3>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">一般情况下我们认为,考虑到前端发的AJAX之类的请求的数据量,是不需要做Gzip压缩的。但是向服务器发起请求的不一定只有前端,还可能是Node的客户端。这些Node客户端可能会向Node服务端传送压缩过后的数据流。&nbsp;例如下面的代码所示</span></p>
<p>&nbsp;</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>&nbsp;</p>
<h2><span style="font-size: 16px; font-family: 楷体">项目的github和npm地址</span></h2>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">https://github.com/penghuwan/body-parser-promise</span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">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&nbsp;https://github.com/koajs/bodyparser</span></p>
<p>&nbsp;</p>
<h2><span style="font-family: 楷体"><span style="font-family: 楷体"><span style="font-size: 18px">上一篇文章</span></span></span></h2>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 16px"><span>如何用JavaScript测网速</span></span></p>
<p><span style="font-size: 16px; font-family: &quot;Microsoft YaHei&quot;">【完】</span></p>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    我叫彭湖湾,请叫我胖湾<br><br>
来源:https://www.cnblogs.com/penghuwan/p/11374268.html
頁: [1]
查看完整版本: 【Node.js】 bodyparser实现原理解析