上回书说到:
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
这不断变化的标准,
或许成为了一些软件表现错误、规则落后的原因
但也成为了督促我们不断更新、不断学习的动力
领取专属 10元无门槛券
私享最新 技术干货