问题现象
rest-assured是一个基于java语言的REST API测试框架,使用简单易上手,目前在github上已经有6.5K stars。日前在使用rest-assured发送multipart/form-data类型的请求时,服务端报参数错误。
问题影响
经过对发送端参数的检查,都是正确的。除了multipart/form-data, POST的其他请求也都正常,按理说不应该出现这样的情况。所以按照如下过程进行排查,也进一步了解了Multipart/form-data和rest-assured的特性。
问题分析过程
问题直接原因定位
分析服务端代码,查看日志。发现已经收到了请求,并且参数@QueryParam已经正确收到,报错发生在对参数content的解析上。
再看了一遍日志打印的收到的请求参数,发现里面出现了?号,类似{"test??":1}。很有可能是中文乱码这个老朋友。为了验证这个猜想,将请求中的中文全部替换为英文,再次发送请求,服务端响应成功。
由于不可能一直不发送中文,问题需要彻底解决。首先了解一下Multipart/form-data内容的格式
Multipart/form-data的简单了解
HTTP协议簇中的媒体类型multipart/form-data,数据体支持多个部分组成,并且每个部分之间用boundary进行分隔,boundary分隔符在请求头中指定
# 请求头 - 指定Content-Type和边界值
Content-Type: multipart/form-data; boundary=${Boundary}
# 请求体
--${Boundary}
Content-Disposition: form-data; name="property name"
Content-Type: application/json
json内容
--${Boundary}
Content-Disposition: form-data; name="name of pdf"; filename="pdf-file.pdf"
Content-Type: application/octet-stream
文件内容,bytes
--${Boundary}
问题发生之处
为了找到中文乱码在什么地方出现,采取一下步骤
-
是否在组装时已经乱码。通过打印请求头来验证,发现IDE中打印出来的字符是正常的
given().log().all()
是否发送的是乱码。使用抓包神器Fiddler来分析报文。由于Fiddler本质是一个代理,从IDE发送的报文不会直接被抓到,还好rest-assured提供了代理模式,按照以下代码设置
given().proxy("127.0.0.1", 8888)
果然,抓到的报文里,中文显示成了乱码
如何设置charset
在rest-assured的官网(官网文档MultiPartConfig - rest-assured 4.0.0 javadoc)上介绍,multipart/form-data默认的编码格式是US-ASCII。首先直接设置.contentType("multipart/form-data;charset=UTF-8"),结果:不生效
后来刨到陈年老坑,有人提问multi-part不支持针对每个Content-Disposition的设置,项目的开发人员记录并回复增加了MultiPartConfig来支持对multi-parts配置需求,于是增加了一下代码。由于multipart的模式默认是HttpMultipartMode.STRICT,因此代码里需要将httpMultipartMode指定为一个其他的值。结果:content-dispositon中的header生效,但是中文内容仍然是乱码。
.httpClient(HttpClientConfig.httpClientConfig().httpMultipartMode(BROWSER_COMPATIBLE))
.multiPartConfig(multiPartConfig().defaultCharset("UTF-8")))
再次阅读文档,里面提到:Specify a default charset to use for multi-parts (default is US-ASCII). This affects the encoding of the multipart body (such as the control name) but not the actual content (such as the a JSON or String document).
说明这个配置只能设置control name的编码格式,不能设置真正发送的内容。从这个方向上继续排查,stack overflow上有人提供了一个解决方法,也就是用MultiPartSpecBuilder来构造发送的内容。结果:成功
.multiPart(new MultiPartSpecBuilder("{\"key\":\"测试test\"}").controlName("params").charset(StandardCharsets.UTF_8).build())
如何解决问题
最终发送请求的代码,调整如下
given().
.headers(myHeader())
.config(RestAssuredConfig.config()
.httpClient(HttpClientConfig.httpClientConfig().httpMultipartMode(BROWSER_COMPATIBLE))
.multiPartConfig(multiPartConfig().defaultCharset("UTF-8")))
.multiPart(new MultiPartSpecBuilder("{\"key\":\"测试test\"}").controlName("params").charset(StandardCharsets.UTF_8).build())
.when()
.post(UrlCollection.urlProductCreate)
.then()
.log().body()