深入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 delete操作符应用实例
Jan 13 Javascript
jQuery遍历Form示例代码
Sep 03 Javascript
让angularjs支持浏览器自动填表
Nov 10 Javascript
javascript面向对象程序设计(一)
Jan 29 Javascript
JS实现仿中关村论坛评分后弹出提示效果的方法
Feb 23 Javascript
JS常见疑难点分析之match,charAt,charCodeAt,map,search用法分析
Dec 25 Javascript
assert()函数用法总结(推荐)
Jan 25 Javascript
浅谈angularjs依赖服务注入写法的注意点
Apr 24 Javascript
vue.js实现刷新当前页面的方法教程
Jul 05 Javascript
11个教程中不常被提及的JavaScript小技巧(推荐)
Apr 17 Javascript
JavaScript实现省份城市的三级联动
Feb 11 Javascript
AJAX实现指定部分页面刷新效果
Oct 16 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 json与xml序列化/反序列化
2013/10/28 PHP
PHP中4种常用的抓取网络数据方法
2015/06/04 PHP
在Mac上编译安装PHP7的开发环境
2015/07/28 PHP
JavaScript中Object和Function的关系小结
2009/09/26 Javascript
Jquery ajaxsubmit上传图片实现代码
2010/11/04 Javascript
让图片旋转任意角度及JQuery插件使用介绍
2013/03/20 Javascript
jQuery+JSON+jPlayer实现QQ空间音乐查询功能示例
2013/06/17 Javascript
ie8模式下click无反应点击option无反应的解决方法
2014/10/11 Javascript
javascript实现节点(div)名称编辑
2014/12/17 Javascript
为什么JavaScript没有块级作用域
2016/05/22 Javascript
深入对Vue.js $watch方法的理解
2017/03/20 Javascript
Angular.JS中指令ng-if、ng-show/ng-hide和ng-switch的使用教程
2017/05/07 Javascript
教你5分钟学会用requirejs(必看篇)
2017/07/25 Javascript
Angular实现双向折叠列表组件的示例代码
2017/11/21 Javascript
vue 实现在函数中触发路由跳转的示例
2018/09/01 Javascript
Node.js事件的正确使用方法
2019/04/05 Javascript
Vue3.0 响应式系统源码逐行分析讲解
2019/10/14 Javascript
js+for循环实现字符串自动转义的代码(把后面的字符替换前面的字符)
2020/12/24 Javascript
[37:03]完美世界DOTA2联赛PWL S3 INK ICE vs GXR 第二场 12.16
2020/12/18 DOTA
python处理文本文件并生成指定格式的文件
2014/07/31 Python
python实现定时同步本机与北京时间的方法
2015/03/24 Python
Python基于回溯法子集树模板解决马踏棋盘问题示例
2017/09/11 Python
Django使用详解:ORM 的反向查找(related_name)
2018/05/30 Python
django将数组传递给前台模板的方法
2019/08/06 Python
pycharm 实现本地写代码,服务器运行的操作
2020/06/08 Python
Python使用lambda抛出异常实现方法解析
2020/08/20 Python
如何使用scrapy中的ItemLoader提取数据
2020/09/30 Python
国贸专业大学生职业生涯规划范文
2014/01/10 职场文书
一位农村小子的自荐信
2014/04/07 职场文书
买房委托公证书
2014/04/08 职场文书
单位授权委托书范文
2014/08/02 职场文书
卫校毕业生自我鉴定
2014/09/28 职场文书
云冈石窟导游词
2015/02/04 职场文书
销售业务员岗位职责
2015/02/13 职场文书
小数乘法教学反思
2016/02/22 职场文书
InterProcessMutex实现zookeeper分布式锁原理
2022/03/21 Java/Android