【Node.js】 bodyparser实现原理解析

为什么我们需要body-parser
body-parser代码逻辑
- data事件:当request接收到数据的时候触发,在数据传输结束前可能会触发多次,在事件回调里可以接收到Buffer类型的数据参数,我们可以将Buffer数据对象收集到数组里
- end事件:请求数据接收结束时候触发,不提供参数,我们可以在这里将之前收集的Buffer数组集中处理,最后输出将request.body输出。
数据处理流程
- 在request的data事件触发时候,收集Buffer对象,将其放到一个命名为chunks的数组中
- 在request的end事件触发时,通过Buffer.concat(chunks)将Buffer数组整合成单一的大的Buffer对象
- 解析请求首部的Content-Encoding,根据类型,如gzip,deflate等调用相应的解压缩函数如Zlib.gunzip,将2中得到的Buffer解压,返回的是解压后的Buffer对象
- 解析请求的charset字符编码,根据其类型,如gbk或者utf-8,调用iconv库提供的decode(buffer, charset)方法,根据字符编码将3中的Buffer转换成字符串
- 最后,根据Content-Type,如application/json或'application/x-www-form-urlencoded'对4中得到的字符串做相应的解析处理,得到最后的对象,作为request.body返回
下面展示下相关的代码
整体代码结构
// 根据Content-Encoding判断是否解压,如需则调用相应解压函数
async function transformEncode(buffer, encode) {
// ...
}
// charset转码
function transformCharset(buffer, charset) {
// ...
} // 根据content-type做最后的数据格式化
function formatData(str, contentType) {
// ...
} // 返回Promise
function getRequestBody(req, res) {
return new Promise(async (resolve, reject) => {
const chunks = [];
req.on('data', buf => {
chunks.push(buf);
})
req.on('end', async () => {
let buffer = Buffer.concat(chunks);
// 获取content-encoding
const encode = req.headers['content-encoding'];
// 获取content-type
const { type, parameters } = contentType.parse(req);
// 获取charset
const charset = parameters.charset;
// 解压缩
buffer = await transformEncode(buffer, encode);
// 转换字符编码
const str = transformCharset(buffer, charset);
// 根据类型输出不同格式的数据,如字符串或JSON对象
const result = formatData(str, type);
resolve(result);
})
}).catch(err => { throw err; })
}
Step0.Promise的编程风格
function getRequestBody(req, res) {
return new Promise(async (resolve, reject) => {
// ...
}
}
Step1.data事件的处理
const chunks = [];
req.on('data', buf => {
chunks.push(buf);
})
Step2.end事件的处理
const contentType = require('content-type');
const iconv = require('iconv-lite');
req.on('end', async () => {
let buffer = Buffer.concat(chunks);
// 获取content-encoding
const encode = req.headers['content-encoding'];
// 获取content-type
const { type, parameters } = contentType.parse(req);
// 获取charset
const charset = parameters.charset;
// 解压缩
buffer = await transformEncode(buffer, encode);
// 转换字符编码
const str = transformCharset(buffer, charset);
// 根据类型输出不同格式的数据,如字符串或JSON对象
const result = formatData(str, type);
resolve(result);
}
Step3.根据Content-Encoding进行解压处理
Content-Encoding可分为四种值:gzip,compress,deflate,br,identity
其中
- identity表示数据保持原样,没有经过压缩
- compress已经被大多数浏览器废弃,Node没有提供解压的方法
所以我们需要处理解压的一共有三种数据类型
- gzip:采用zlib.gunzip方法解压
- deflate: 采用zlib.inflate方法解压
- br:采用zlib.brotliDecompress方法解压
(注意!zlib.brotliDecompress方法在Node11.7以上版本才会支持,而且不要看到名字里有compress就误以为它是用来解压compress压缩的数据的,实际上它是用来处理br的)
代码如下,我们对zlib.gunzip等回调类方法通过promisify转成Promise编码风格
const promisify = util.promisify;
// node 11.7版本以上才支持此方法
const brotliDecompress = zlib.brotliDecompress && promisify(zlib.brotliDecompress); const gunzip = promisify(zlib.gunzip);
const inflate = promisify(zlib.inflate); const querystring = require('querystring'); // 根据Content-Encoding判断是否解压,如需则调用相应解压函数
async function transformEncode(buffer, encode) {
let resultBuf = null;
debugger;
switch (encode) {
case 'br':
if (!brotliDecompress) {
throw new Error('Node版本过低! 11.6版本以上才支持brotliDecompress方法')
}
resultBuf = await brotliDecompress(buffer);
break;
case 'gzip':
resultBuf = await gunzip(buffer);
break;
case 'deflate':
resultBuf = await inflate(buffer);
break;
default:
resultBuf = buffer;
break;
}
return resultBuf;
}
Step4.根据charset进行转码处理
我们采用iconv-lite对charset进行转码,代码如下
const iconv = require('iconv-lite');
// charset转码
function transformCharset(buffer, charset) {
charset = charset || 'UTF-8';
// iconv将Buffer转化为对应charset编码的String
const result = iconv.decode(buffer, charset);
return result;
}
来!传送门
https://link.zhihu.com/?target=https%3A//www.npmjs.com/package/iconv-lite
Step5.根据contentType将4中得到的字符串数据进行格式化
具体的处理方式分三种情况:
- 对text/plain 保持原样,不做处理,仍然是字符串
- 对application/x-www-form-urlencoded,得到的是类似于key1=val1&key2=val2的数据,通过querystring模块的parse方法转成{ key:val }结构的对象
- 对于application/json,通过JSON.parse(str)一波带走
代码如下
const querystring = require('querystring');
// 根据content-type做最后的数据格式化
function formatData(str, contentType) {
let result = '';
switch (contentType) {
case 'text/plain':
result = str;
break;
case 'application/json':
result = JSON.parse(str);
break;
case 'application/x-www-form-urlencoded':
result = querystring.parse(str);
break;
default:
break;
}
return result;
}
测试代码
服务端
下面的代码你肯定知道要放在哪里了
// 省略其他代码
if (pathname === '/post') {
// 调用getRequestBody,通过await修饰等待结果返回
const body = await getRequestBody(req, res);
console.log(body);
return;
}
前端采用fetch进行测试
在下面的代码中,我们连续三次发出不同的POST请求,携带不同类型的body数据,看看服务端会输出什么
var iconv = require('iconv-lite');
var querystring = require('querystring');
var gbkBody = {
data: "我是彭湖湾",
contentType: 'application/json',
charset: 'gbk'
};
// 转化为JSON数据
var gbkJson = JSON.stringify(gbkBody);
// 转为gbk编码
var gbkData = iconv.encode(gbkJson, "gbk");
var isoData = iconv.encode("我是彭湖湾,这句话采用UTF-8格式编码,content-type为text/plain", "UTF-8")
// 测试内容类型为application/json和charset=gbk的情况
fetch('/post', {
method: 'POST',
headers: {
"Content-Type": 'application/json; charset=gbk'
},
body: gbkData
});
// 测试内容类型为application/x-www-form-urlencoded和charset=UTF-8的情况
fetch('/post', {
method: 'POST',
headers: {
"Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: querystring.stringify({
data: "我是彭湖湾",
contentType: 'application/x-www-form-urlencoded',
charset: 'UTF-8'
})
});
// 测试内容类型为text/plain的情况
fetch('/post', {
method: 'POST',
headers: {
"Content-Type": 'text/plain; charset=UTF-8'
},
body: isoData
});
服务端输出结果
{
data: '我是彭湖湾',
contentType: 'application/json',
charset: 'gbk'
}
{
data: '我是彭湖湾',
contentType: 'application/x-www-form-urlencoded',
charset: 'UTF-8'
}
我是彭湖湾,这句话采用UTF-8格式编码,content-type为text/plain
问题和后记
Q1.为什么要对charset进行处理
其实本质上来说,charset前端一般都是固定为utf-8的, 甚至在JQuery的AJAX请求中,前端请求charset甚至是不可更改,只能是charset,但是在使用fetch等API的时候,的确是可以更改charset的,这个工作尝试满足一些比较偏僻的更改charset需求。
Q2:为什么要对content-encoding做处理呢?
一般情况下我们认为,考虑到前端发的AJAX之类的请求的数据量,是不需要做Gzip压缩的。但是向服务器发起请求的不一定只有前端,还可能是Node的客户端。这些Node客户端可能会向Node服务端传送压缩过后的数据流。 例如下面的代码所示
const zlib = require('zlib');
const request = require('request');
const data = zlib.gzipSync(Buffer.from("我是一个被Gzip压缩后的数据"));
request({
method: 'POST',
url: 'http://127.0.0.1:3000/post',
headers: {//设置请求头
"Content-Type": "text/plain",
"Content-Encoding": "gzip"
},
body: data
})
项目的github和npm地址
https://github.com/penghuwan/body-parser-promise
https://www.npmjs.com/package/body-parser-promise
参考资料
Koa-bodyparser https://github.com/koajs/bodyparser
上一篇文章
【完】
【Node.js】 bodyparser实现原理解析的更多相关文章
- 深入研究Node.js的底层原理和高级使用
深入研究Node.js的底层原理和高级使用
- vue.js响应式原理解析与实现
vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...
- node.js基本工作原理及流程
概述 Node.js是什么 Node 是一个服务器端 JavaScript 解释器,用于方便地搭建响应速度快.易于扩展的网络应用.Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非 ...
- node.js官方文档解析 02—buffer 缓冲器
Buffer 类的实例类似于整数数组,但 Buffer 的大小是固定的.且在 V8 堆外分配物理内存.Buffer 的大小在被创建时确定,且无法调整. Buffer 类在 Node.js 中是一个全局 ...
- Node.js异步IO原理剖析
为什么要异步I/O? 从用户体验角度讲,异步IO可以消除UI阻塞,快速响应资源 JavaScript是单线程的,它与UI渲染共用一个线程.所以在JavaScript执行的时候,UI渲染将处于停顿的状态 ...
- Node.js机制及原理理解初步【转】
一.node.js优缺点 node.js是单线程. 好处就是 1)简单 2)高性能,避免了频繁的线程切换开销 3)占用资源小,因为是单线程,在大负荷情况下,对内存占用仍然很低 3)线程安全,没有加锁. ...
- Node.js机制及原理理解初步
http://blog.csdn.net/leftfist/article/details/41891407 一.node.js优缺点 node.js是单线程. 好处就是 1)简单 2)高性能,避免了 ...
- vue.js响应式原理解析与实现—实现v-model与{{}}指令
上一节我们已经分析了vue.js是通过Object.defineProperty以及发布订阅模式来进行数据劫持和监听,并且实现了一个简单的demo.今天,我们就基于上一节的代码,来实现一个MVVM类, ...
- node.js官方文档解析 01—assert 断言
assert-------断言 new assert.AssertionError(options) Error 的一个子类,表明断言的失败. options(选项)有下列对象 message < ...
随机推荐
- cogs.12运输问题2题解
乍一看貌似和运输问题1没有任何区别,但本题有一个有意思的东西叫做下限,我个人称之为非强制下限,因为本题中要求的实际是我走这条边这条边才至少走下限的流,虽然出题人没说,但从样例来看确实是这样的,而强制下 ...
- MyBatis 一对多映射
From<MyBatis从入门到精通> <!-- 6.1.2.1 collection集合的嵌套结果映射 和association类似,集合的嵌套结果映射就是指通过一次SQL查询将所 ...
- javascript案例之放大镜效果
效果图 如何实现该效果呢?? 我们先来进行分析 实现思路 1.鼠标移入移出事件 1>移入:悬浮块和大图显示 2>移出:悬浮块和大图隐藏 2.鼠标移动(悬浮块随着鼠标移动) 1>获 ...
- Python学习2——Python单行注释、整段注释使用方法
Python中的注释有多种,有单行注释,多行注释,批量注释,中文注释也是常用的. python注释也有自己的规范,在文章中会介绍到. 注释可以起到一个备注的作用,团队合作的时候,个人编写的代码经常会被 ...
- [PTA] 数据结构与算法题目集 6-2 顺序表操作集
//创建并返回一个空的线性表: List MakeEmpty() { List L; L = (List)malloc(sizeof(struct LNode)); L->Last = -1; ...
- Amdahl定律理解
其中,a为并行计算部分所占比例,k为并行处理的个数. 当1-a=0时,(没有串行,只有并行)最大加速比s=n: 当a=0时,(只有串行,没有并行)最小加速比s=1: 当k→∞时,s → 1 /(1-a ...
- 从0系统学Android-2.4隐式Intent
本系列文章,参考<第一行代码>,作为个人笔记 更多内容:更多精品文章分类 使用隐式 Intent 相对于显示 Intent ,隐式 Intent 比较含蓄.这种方式不明确指出我们想要启动哪 ...
- 《css的总结》
一.span标签:能让某几个文字或者某个词语凸显出来 <p> 今天是11月份的<span>第一天</span>,地铁卡不打折了 </p> 二.字体风格 ...
- JS原生隐士标签扩展
最近项目开发中,开发了不少的接口,有一个接口是这样子的.先从A公司拿到数据后,存放到我们公司数据库里,然后需要将数据展示给客户,下面这个界面,后台要实时刷新,后台写了个定时器,2S刷一次从后台拼接好H ...
- Nginx 的简单使用 (IIS,Asp.Net)
Nginx 的一些常见功能(windows,AspNet ,IIS) 下载 官方网站:https://nginx.org/en/download.html 下载,解压缩是这个样子 启动: 启动方式有两 ...