首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

记一次JMeter文件上传失败的debug过程(续)

上回书说到:

JMeter 在请求头中 `Content-Types 添加中多了以下内容,导致文件上传失败。

; charset=UTF-8

在解决问题之后,我不禁产生了强烈的好奇:

究竟是错在 JMeter 不该发送 charset,还是错在微信接口不能处理 charset?

于是,我通过大量的案例搜索和对 HTTP 规范的研读找到答案,

仅以此文供大家参考

1. content-type 不能发送 charset 吗

以下面的 为关键字进行搜索,可以得到大量的参考资料

content-type charset

总体来看,content-type和charset在一起是合理。

来自 MDN 的结果

来自 w3c 的结果

也符合大部分人的直观认知:

表单可以提交文字,为了避免乱码,传递 charset 是合理的也是必要的

若以此为依据,则 JMeter 的表现看起来是对的

2. 只有微信接口不能接受 charset 吗

但是,JMeter 是对的,

那等于说微信是。。错的?

作为一个大厂,应该不至于出现这么低级的错误,为了避免武断,

我以下面的内容作为关键字继续进行搜索

http content-type boundary "charset"

接下来一个高度相似的案例

charset 引发异常

该贴的讨论内容包含了大量的重要信息,简要摘录如下:

1,某开发者在使用国外某服务时,如果请求头 content-type 传递 charset 会出现错误,不传递 charset 则正常

2,进一步发现,如果 boundary 在前,charset 在后时,会出错,比如这样

Content-Type: multipart/form-data;boundary=059h2BBM-KlM_XP2rY8W1X3_jnzFLcYY;charset=UTF-8

但如果反过来 charset 在后时,boundary 在后,不会出错, 比如这样

Content-Type: multipart/form-data;charset=UTF-8;boundary=059h2BBM-KlM_XP2rY8W1X3_jnzFLcYY

微信接口的表现于此相同

3,推测原因:boundary 在前、charset 在后时,服务器没有正确识别 boundary,导致无法正确地从请求中获取文件起始和结束位置,于是提示:文件缺失(未上传)

4,服务器该行为是否合理需要依照RFC 进行判定

5,为了抑制该错误,该项目(spring-framework)采取了将 charset 放置在 boundary 之前作为临时处理方案

此外,早在七年前就有人在 JMeter 2.x 的时候进行过类似讨论

jmeter 2.x 发送 charset 引发错误

从这些讨论中可以看出:

并非只有微信的接口这样,也不最近偶尔这样,而是国内国外、过去现在,普遍出现类似现象。

所以这个不仅仅是 JMeter 和微信之间的对错

而是全世界一部分软件(服务器),与另一部分软件(服务器),两大群体之间差异

作为吃瓜群众,这个升华我先赞为敬。。。

3. HTTP 协议中对此是如何约定的

我们一直说 HTTP 协议很简单,主要是因为:

它的格式内容以文本为主,确实比较容易理解;

海量的实际案例,方便进行尝试、探索、验证;

规范公开且统一,可供任何人从源头修正偏差;

所以我打算从 HTTP 协议规范入手,辨明到底谁是谁非

首先,查阅了 RFC 7231《HTTP 语义和内容》

《RFC 7231》首页

在 RFC 7231 [3.1.1.1] 中约定:

资源的类型描述中允许传递参数,例如:text/html;charset=utf-8中的 charset 就是参数

在 RFC 7231 [3.1.1.4] 中约定:

请求头使用multipart/form-data的细则,描述于 [RFC 2338](已废弃,现被 RFC 7578 取代)

在 RFC 7231 [3.1.1.5] 中约定:

Content-Type请求头的正确值,描述于 [RFC 2046]

接着,查阅 RFC 7578 《从表单取值:multipart/form-data》

《RFC 7578》 首页

在 RFC 7578 [4.2] 中 制定了multipart/form-data的语法规则:

1,multipart/form-data用于在一个 HTTP 正文中传递多份表单数据

2,每份数据之间,使用请求头Content-type中的boundary作为分隔符

3,每份数据必须包含Content-Disposition标头,发送字段名(必填)和文件名(选填)

4,每份数据可以包含Content-Type标头,默认为text/plain,如果是文件则应该改为文件的 MIME

5,文本数据 的Content-Type标头可以发送charset参数,声明字符集

6,表单中的默认字符集,通过特殊字典_charset_发送 (而不是请求头中的charset)

在 RFC 7578 [5.1.2],专门制定了表单编码(form-charset)的确定规则:

如果存在_charset_字段,则使用其值;

如果存在元素的 accept-charset 属性,则使用其值;

如果包含表单的文档的字符编码与 US-ASCII 兼容,则使用文档的字符编码;

否则,默认使用 UTF-8

显而易见,对于multipart/form-data来说:

charset不应该添加到请求头,而是添加到正文,

或者干脆不发送 charset,使用默认值 UTF-8

随后,查阅 RFC 2046 《MIME : 媒体类型》

《RFC 2046》 首页

在 RFC 2046 [4.1.2] 中约定:

”text“类型(例如text/plain)的消息可以在Content-Type中传递charset参数

charset参数值不分大小写,默认为US-ASCII

任何text的子类型,可以使用charset参数,并且语义与在text/plain中的完全相同

即,正文完全由 charset 中的字符组成

显而易见,对于multipart/form-data来说:

它是multipart的子类型,而不是text子类型,所以不符合第一点;

它可以上传文件,正文包含二进制文件,而不是纯字符,所以不符合第四点;

综上所述,我们可以得到一个确切的结论

JMeter 构造的以下请求头是不正确的,是不符合 HTTP 协议规范的

Content-Type: multipart/form-data; boundary=2W1aSJ1TtJC_jRaGnbotI-RaHchFMAO; charset=UTF-8

原因:

multipart非text子类型, 不需要发送charset参数

multipart的默认字符集正是utf-8`,不需发送

multipart的字符集参数位于请求正文,而不是请求头

multipart的字符集参数名是_charset_,而不是charset

同理,下面这个常见的请求头可能也不正确

Content-type: application/json; charset=utf-8

于是,查阅了 RFC 8259《JSON 数据交换格式》

《RFC 8259》首页

在 RFC 8259 [8.1] 中约定:

在非封闭系统的系统之间交换的 JSON 文本生态系统必须使用 UTF-8

所以:

application/json并不是text子类型, 不需要发送charset参数

application/json强制统一使用utf-8作为字符集,不需发送 utf-8

果然如此,吃瓜结束

如果兴趣的话,可以挖掘:

是 JMeter 错误的传递了请求头?

还是 HTTPClient 错误地传递了请求头?

是否有更加优雅的方式在JMeter中请求成功?

4. 彩蛋

在研读 RFC 的过程中发现:标准规范是在持续更新不断变化的。

比如 JSON 使用哪种字符编码,就有过前后矛盾的演变

RFC 4627 [3](2007 年):

JSON 可以使用 UTF-8,UTF-16 或 UTF-32,通过正文起始的标记字符判断

RFC 7159 [8.1] (2013 年):

JSON 可以使用 UTF-8,UTF-16 或 UTF-32,但不得将标记字符加在正文

RFC 8259 [8.1] (2017 年):

非私有隔离的环境下 JSON 必须使用 UTF-8

这不断变化的标准,

或许成为了一些软件表现错误、规则落后的原因

但也成为了督促我们不断更新、不断学习的动力

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OAGNIZ0DU9oxWuiZjh_FIcTw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券