深入koa-bodyparser原理解析


Posted in Javascript onJanuary 16, 2019

一、前置知识

在理解koa-bodyparser原理之前,首先需要了解部分HTTP相关的知识。

1、报文主体

HTTP报文主要分为请求报文和响应报文,koa-bodyparser主要针对请求报文的处理。

请求报文主要由以下三个部分组成:

  • 报文头部
  • 空行
  • 报文主体

而koa-bodyparser中的body指的就是请求报文中的报文主体部分。

2、服务器端获取报文主体流程

HTTP底层采用TCP提供可靠的字节流服务,简单而言就是报文主体部分会被转化为二进制数据在网络中传输,所以服务器端首先需要拿到二进制流数据。

谈到网络传输,当然会涉及到传输速度的优化,而其中一种优化方式就是对内容进行压缩编码,常用的压缩编码方式有:

  • gzip
  • compress
  • deflate
  • identity(不执行压缩或不会变化的默认编码格式)

服务器端会根据报文头部信息中的Content-Encoding确认采用何种解压编码。

接下来就需要将二进制数据转换为相应的字符,而字符也有不同的字符编码方式,例如对于中文字符处理差异巨大的UTF-8和GBK,UTF-8编码汉字通常需要三个字节,而GBK只需要两个字节。所以还需要在请求报文的头部信息中设置Content-Type使用的字符编码信息(默认情况下采用的是UTF-8),这样服务器端就可以利用相应的字符规则进行解码,得到正确的字符串。

拿到字符串之后,服务器端又要问了:客户端,你这一段字符串是啥意思啊?

根据不同的应用场景,客户端会对字符串采用不同的编码方式,常见的编码方式有:

  • URL编码方式: a=1&b=2
  • JSON编码方式: {a:1,b:2}

客户端会将采用的字符串编码方式设置在请求报文头部信息的Content-Type属性中,这样服务器端根据相应的字符串编码规则进行解码,就能够明白客户端所传递的信息了。

下面一步步分析koa-bodyparser是如何处理这一系列操作,从而得到报文主体内容。

二、获取二进制数据流

NodeJS中获取请求报文主体二进制数据流主要通过监听request对象的data事件完成:

// 示例一
const http = require('http')

http.createServer((req, res) => {
 const body = []

 req.on('data', chunk => {
  body.push(chunk)
 })
 
 req.on('end', () => {
  const chunks = Buffer.concat(body) // 接收到的二进制数据流

  // 利用res.end进行响应处理
  res.end(chunks.toString())
 })
}).listen(1234)

而koa-bodyparser主要是对co-body 的封装,而【co-body】中主要是采用raw-body 模块获取请求报文主体的二进制数据流,【row-body】主要是对上述示例代码的封装和健壮性处理。

三、内容解码

客户端会将内容编码的方式放入请求报文头部信息Content-Encoding属性中,服务器端接收报文主体的二进制数据了时,会根据该头部信息进行解压操作,当然服务器端可以在响应报文头部信息Accept-Encoding属性中添加支持的解压方式。

而【row-body】主要采用 inflation 模块进行解压处理。

四、字符解码

一般而言,UTF-8是互联网中主流的字符编码方式,前面也提到了还有GBK编码方式,相比较UTF-8,它编码中文只需要2个字节,那么在字符解码时误用UTF-8解码GBK编码的字符,就会出现中文乱码的问题。

NodeJS主要通过Buffer处理二进制数据流,但是它并不支持GBK字符编码方式,需要通过 iconv-lite 模块进行处理。

【示例一】中的代码就存在没有正确处理字符编码的问题,那么报文主体中的字符采用GBK编码方式,必然会出现中文乱码:

const request = require('request')
const iconv = require('iconv-lite')

request.post({
 url: 'http://localhost:1234/',
 body: iconv.encode('中文', 'gbk'),
 headers: {
  'Content-Type': 'text/plain;charset=GBK'
 }
}, (error, response, body) => {
 console.log(body) // 发生中文乱码情况
})

NodeJS中的Buffer默认是采用UTF-8字符编码处理,这里借助【iconv-lite】模块处理不同的字符编码方式:

const chunks = Buffer.concat(body)
  res.end(iconv.decode(chunks, charset)) // charset通过Content-Type得到

五、字符串解码

前面已经提到了字符串的二种编码方式,它们对应的Content-Type分别为:

  • URL编码 application/x-www-form-urlencoded
  • JSON编码 application/json

对于前端来说,URL编码并不陌生,经常会用于URL拼接操作,唯一需要注意的是不要忘记对键值对进行decodeURIComponent()处理。

当客户端发送请求主体时,需要进行编码操作:

'a=1&b=2&c=3'

服务器端再根据URL编码规则解码,得到相应的对象。

// URL编码方式 简单的解码方法实现
function decode (qs, sep = '&', eq = '=') {
 const obj = {}
 qs = qs.split(sep)

 for (let i = 0, max = qs.length; i < max; i++) {
  const item = qs[i]
  const index = item.indexOf(eq)

  let key, value

  if (~index) {
   key = item.substr(0, index)
   value = item.substr(index + 1)
  } else {
   key = item
   value = ''
  }
  
  key = decodeURIComponent(key)
  value = decodeURIComponent(value)

  if (!obj.hasOwnProperty(key)) {
   obj[key] = value
  }
 }
 return obj
}

console.log(decode('a=1&b=2&c=3')) // { a: '1', b: '2', c: '3' }

URL编码方式适合处理简单的键值对数据,并且很多框架的Ajax中的Content-Type默认值都是它,但是对于复杂的嵌套对象就不太好处理了,这时就需要JSON编码方式大显身手了。

客户端发送请求主体时,只需要采用JSON.stringify进行编码。服务器端只需要采用JSON.parse进行解码即可:

const strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;
function parse(str) {
 if (!strict) return str ? JSON.parse(str) : str;
 // 严格模式下,总是返回一个对象
 if (!str) return {};
 // 是否为合法的JSON字符串
 if (!strictJSONReg.test(str)) {
  throw new Error('invalid JSON, only supports object and array');
 }
 return JSON.parse(str);
}

除了上述两种字符串编码方式,koa-bodyparser还支持不采用任何字符串编码方式的普通字符串。

三种字符串编码的处理方式由【co-body】模块提供,koa-bodyparser中通过判断当前Content-Type类型,调用不同的处理方式,将获取到的结果挂载在ctx.request.body:

return async function bodyParser(ctx, next) {
  if (ctx.request.body !== undefined) return await next();
  if (ctx.disableBodyParser) return await next();
  try {
   // 最重要的一步 将解析的内容挂载到koa的上下文中
   const res = await parseBody(ctx);
   ctx.request.body = 'parsed' in res ? res.parsed : {};
   if (ctx.request.rawBody === undefined) ctx.request.rawBody = res.raw; // 保存原始字符串
  } catch (err) {
   if (onerror) {
    onerror(err, ctx);
   } else {
    throw err;
   }
  }
  await next();
 };

 async function parseBody(ctx) {
  if (enableJson && ((detectJSON && detectJSON(ctx)) || ctx.request.is(jsonTypes))) {
   return await parse.json(ctx, jsonOpts); // application/json等json type
  }
  if (enableForm && ctx.request.is(formTypes)) {
   return await parse.form(ctx, formOpts); // application/x-www-form-urlencoded
  }
  if (enableText && ctx.request.is(textTypes)) {
   return await parse.text(ctx, textOpts) || ''; // text/plain
  }
  return {};
 }
};

其实还有一种比较常见的Content-type,当采用表单上传时,报文主体中会包含多个实体主体:

------WebKitFormBoundaryqsAGMB6Us6F7s3SF
Content-Disposition: form-data; name="image"; filename="image.png"
Content-Type: image/png


------WebKitFormBoundaryqsAGMB6Us6F7s3SF
Content-Disposition: form-data; name="text"

------WebKitFormBoundaryqsAGMB6Us6F7s3SF--

这种方式处理相对比较复杂,koa-bodyparser中并没有提供该Content-Type的解析。(下一篇中应该会介绍^_^)

五、总结

以上便是koa-bodyparser的核心实现原理,其中涉及到很多关于HTTP的基础知识,对于HTTP不太熟悉的同学,可以推荐看一波入门级宝典【图解HTTP】。

最后留图一张:

深入koa-bodyparser原理解析 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript实现Sleep函数的代码
Mar 04 Javascript
终于解决了IE8不支持数组的indexOf方法
Apr 03 Javascript
Blocksit插件实现瀑布流数据无限( 异步)加载
Jun 20 Javascript
KnockoutJS 3.X API 第四章之数据控制流with绑定
Oct 10 Javascript
Vue.js组件tab实现选项卡切换
Mar 23 Javascript
Bootstrap页面标题Page Header的实现方法
Mar 22 Javascript
微信小程序中的onLoad详解及简单实例
Apr 05 Javascript
详解JS中的柯里化(currying)
Aug 17 Javascript
angularjs实现猜大小功能
Oct 23 Javascript
使用angularjs.foreach时return的问题解决
Sep 30 Javascript
如何在微信小程序中存setStorage
Dec 13 Javascript
node.js Promise对象的使用方法实例分析
Dec 26 Javascript
jQuery实现的点击图片居中放大缩小功能示例
Jan 16 #jQuery
详解微信小程序之scroll-view的flex布局问题
Jan 16 #Javascript
vue开发环境配置跨域的方法步骤
Jan 16 #Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
Jan 16 #Javascript
在Create React App中启用Sass和Less的方法示例
Jan 16 #Javascript
在Create React App中使用CSS Modules的方法示例
Jan 15 #Javascript
Vue 样式绑定的实现方法
Jan 15 #Javascript
You might like
深入php中var_dump方法的使用详解
2013/06/24 PHP
php创建多级目录与级联删除文件的方法示例
2019/09/12 PHP
jQuery 通过事件委派一次绑定多种事件,以减少事件冗余
2010/06/30 Javascript
js获取事件源及触发该事件的对象
2013/10/24 Javascript
NodeJS Express框架中处理404页面一个方式
2014/05/28 NodeJs
jquery和css3实现的炫酷时尚的菜单导航
2014/09/01 Javascript
javascript获取当前鼠标坐标的方法
2015/01/10 Javascript
AngularJs基本特性解析(一)
2016/07/21 Javascript
JS判断是否在微信浏览器打开的简单实例(推荐)
2016/08/24 Javascript
谈谈JS中常遇到的浏览器兼容问题和解决方法
2016/12/17 Javascript
使用vue2实现带地区编号和名称的省市县三级联动效果
2018/11/05 Javascript
jQuery的ztree仿windows文件新建和拖拽功能的实现代码
2018/12/05 jQuery
详解50行代码,Node爬虫练手项目
2019/04/22 Javascript
通过实例解析js简易模块加载器
2019/06/17 Javascript
Vue作用域插槽实现方法及作用详解
2020/07/08 Javascript
jquery插件实现轮播图效果
2020/10/19 jQuery
[03:27]《辉夜杯》线下训练营 导师CU和海涛指点迷津
2015/10/23 DOTA
用python + hadoop streaming 分布式编程(一) -- 原理介绍,样例程序与本地调试
2014/07/14 Python
python实现无证书加密解密实例
2014/10/27 Python
python分割列表(list)的方法示例
2017/05/07 Python
python消费kafka数据批量插入到es的方法
2018/12/27 Python
python 与服务器的共享文件夹交互方法
2018/12/27 Python
​如何愉快地迁移到 Python 3
2019/04/28 Python
Python爬虫工具requests-html使用解析
2020/04/29 Python
python3列表删除大量重复元素remove()方法的问题详解
2021/01/04 Python
HTML5实现文件断点续传的方法
2017/01/04 HTML / CSS
STUBHUB日本:购买和出售全球活动门票
2018/07/01 全球购物
iHerb中文官网:维生素、保健品和健康产品
2018/11/01 全球购物
YBF Beauty官网:美丽挚友,美国知名彩妆品牌
2020/11/22 全球购物
C语言面试题
2013/05/19 面试题
初中班级口号
2014/06/09 职场文书
校本培训个人总结
2015/02/28 职场文书
2015秋学期开学寄语
2015/05/28 职场文书
赢在中国观后感
2015/06/02 职场文书
2019年中,最受大众欢迎的6本新书
2019/08/07 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL