Apache HttpClient 读取响应乱码问题总结
起因
最近公司产品线研发人员调整,集中兵力做战略产品,现在稳定产品迭代放慢。新的产品线当前有一个最初的版本,为了尽快了解业务,以 API 为入口,以 API 测试为手段,梳理当前版本的业务流程。
在通过 HttpClient 对 API 进行访问时,发现返回的字符串中包含的中文为乱码
环境
- JDK 1.8
- Servlet 3.0.x
- HttpClient 4.2.1
排查
疑因1
由于我们对 HttpClient 进行了再次封装,封装中使用 UTF-8 对响应数据进行编码,之前其它产品线也都使用此 Jar 进行后端 Http API 访问,均未出现乱码的情况,所以初步怀疑是服务没有设置响应编码为 UTF-8
HttpClient 封装代码如下:
HttpEntity entity = response.getEntity();
if (code >= 200 && code < 400) {
return EntityUtils.toString(entity, Charset.forName("UTF-8"));
} else {
EntityUtils.consume(entity);
throw new IllegalArgumentException("请检查连接是否正确,http return code=" + code);
}
查看服务端代码,发现设置了响应的字符编码
response.setCharacterEncoding("UTF-8");
这样服务端编码与客户端编码都是 UTF-8,理论上不应出现乱码的情况。
继续看代码,发现后端代码没有设置响应的 Content-Type,加上如下代码:
response.setContentType("application/json;charset=utf-8");
再次进行测试,发现中文显示正常。
疑因2
排除掉疑因1后,怀疑 setContentType() 方法和 setCharacterEncoding() 方法的处理不一致,遂查看源码,如下:
发现 setContentType 方法内部也会调用 setCharacterEncoding 方法,唯一的区别就是 setContentType 方法设置了 Content-Type 头信息
疑因3
排除掉2后,怀疑是 HttpClinet 对 ContentType 的处理有问题,并且 EntityUtils.toString(entity, Charset.forName("UTF-8"));
中的编码没有起作用。
查看其源码,如下:
从图1中可以看出来,如果没有设置响应 ContentType,它会设置一个默认的 ContentType,从图2中可以看来,设置的默认
Content-Type : text/plain;Charset=ISO-8859-1
Httpclient 会优先使用 ContentType 的编码,只在 ContentType 编码取不到的情况下,才会使用传入的编码(defaultCharset),而默认的 ContentType 始终带有编码(ISO-8859-1)。
所以,当服务端未显式设置 ContentType 时,Httpclient 会使用 ISO-8859-1 编码格式对响应数据进行编码,而不是显式传入的 UTF-8 编码,所以中文会出现乱码。
总结
问题找到原因了,就好办了。
我们只需要显示设置服务端响应 Content-Type 即可,而且这样可以避免通过浏览器访问接口时出现乱码,兼容性更好。
个人认为这是 HttpClient 的一个 bug ,本想给官方提个 issue,但没找到提 Bug 的入口(笑哭),如有人知道,烦请告知,不胜感激!