Apache HttpClient5 Kotlin多部分请求编码字符集问题排查
Apache HttpClient 5.1.3 MultipartEntity 字节解析问题解决方案
问题描述
将HTTP REST客户端从Ktor切换至Apache HttpClient 5.1.3后,MultipartEntity处理二进制数据时出现异常:
- Base64字符串解码为字节数组后,请求Payload中被错误插入DEL字符(0x7f),导致与Ktor/Postman发送的请求不一致
- 日志对比:
Ktor侧:[0xffffffc5]5[0x1d]
HttpClient侧:[0xffffffc5][0x7f]5[0x1d] - 调用验证接口时返回200 OK,但响应体异常,错误内容如下:
{ "graph": [], "input": { "value": null }, "report": { "messages": [ { "name": "DETECT_INPUT_TYPE", "success": false, "result": "<class 'TypeError'> ['TypeError: expected string or bytes-like object\n']", "messageLevel": "ERROR" } ], "errorCount": 1, "warningCount": 0, "valid": false } }
当前构建多部分请求的代码:
inline fun <reified T> executeFormMultipartPost( url: String, bodyForm: ArrayList<NameValuePair>, headers: Map<String, MutableList<String>> = mapOf() ): T { val httpPost = HttpPost(url) for (header in headers) { header.value.forEach { value -> httpPost.addHeader(header.key, value) } } httpPost.addHeader("Accept-Charset", Charsets.UTF_8) var nvParams: ArrayList<NameValuePair> = ArrayList<NameValuePair>() for (n in bodyForm){ nvParams.add(n) } var builder: MultipartEntityBuilder = MultipartEntityBuilder.create().setContentType(ContentType.create( "multipart/form-data")) builder.setMode(HttpMultipartMode.EXTENDED) builder.setMimeSubtype("form-data") val contentDisposition = nvParams[0].value val bytes = BaseEncoding.base64().decode(nvParams[1].value); val image = ByteArrayBody(bytes, ContentType.create( "multipart/form-data"),"badgeimage") val data = StringBody(nvParams[2].value, ContentType.create( "multipart/form-data")) builder.addPart(nvParams[1].name, image) builder.addPart(nvParams[2].name, data) val httpEntity = builder.build() httpPost.entity = httpEntity val response = client.execute(httpPost) return returnResponse(url, response) }
解决方案
1. 修正二进制内容的ContentType设置
ByteArrayBody用于传递二进制文件(如图片),不能设置为multipart/form-data,需改为对应图片的MIME类型,比如image/png或image/jpeg,根据实际图片格式调整:
// 替换原ByteArrayBody创建代码 val image = ByteArrayBody(bytes, ContentType.create("image/png"), "badgeimage")
2. 清理MultipartEntityBuilder的错误配置
- 移除
setMimeSubtype("form-data"):MultipartEntityBuilder默认的子类型就是正确的,手动设置会破坏多部分请求的格式 - 调整Multipart模式:如果EXTENDED模式导致解析异常,可尝试切换为
BROWSER_COMPATIBLE模式,更贴近Postman的请求格式:
var builder = MultipartEntityBuilder.create() .setContentType(ContentType.MULTIPART_FORM_DATA) .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
3. 修正Base64解码逻辑
确保Base64字符串没有被篡改,优先使用Java标准库的Base64解码器替代第三方库,避免解码差异:
// 替换原Base64解码代码 val bytes = Base64.getDecoder().decode(nvParams[1].value)
4. 移除冗余配置与代码
- 删除
Accept-Charset头:该头针对文本请求,二进制文件请求不需要此配置,可能干扰服务端解析 - 移除冗余的参数复制逻辑:直接使用传入的
bodyForm即可,无需重新创建nvParams
修改后的完整代码
inline fun <reified T> executeFormMultipartPost( url: String, bodyForm: ArrayList<NameValuePair>, headers: Map<String, MutableList<String>> = mapOf() ): T { val httpPost = HttpPost(url) // 处理请求头 headers.forEach { (key, values) -> values.forEach { value -> httpPost.addHeader(key, value) } } // 构建多部分实体 val builder = MultipartEntityBuilder.create() .setContentType(ContentType.MULTIPART_FORM_DATA) .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) // 解析Base64图片数据 val imageBytes = Base64.getDecoder().decode(bodyForm[1].value) val imageBody = ByteArrayBody(imageBytes, ContentType.create("image/png"), "badgeimage") builder.addPart(bodyForm[1].name, imageBody) // 处理文本参数,指定UTF-8字符集 val dataBody = StringBody(bodyForm[2].value, ContentType.TEXT_PLAIN.withCharset(Charsets.UTF_8)) builder.addPart(bodyForm[2].name, dataBody) httpPost.entity = builder.build() val response = client.execute(httpPost) return returnResponse(url, response) }
内容的提问来源于stack exchange,提问作者Carlos Andres
相关产品推荐
相关产品推荐

