Apache HttpClient 读取响应乱码问题总结

Apache HttpClient 读取响应乱码问题总结

setCharacterEncoding  Content-Type  HttpClient 

起因

最近公司产品线研发人员调整,集中兵力做战略产品,现在稳定产品迭代放慢。新的产品线当前有一个最初的版本,为了尽快了解业务,以 API 为入口,以 API 测试为手段,梳理当前版本的业务流程。

在通过 HttpClient 对 API 进行访问时,发现返回的字符串中包含的中文为乱码

环境

  1. JDK 1.8
  2. Servlet 3.0.x
  3. 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() 方法的处理不一致,遂查看源码,如下:

Apache HttpClient 读取响应乱码问题总结
enter description here

发现 setContentType 方法内部也会调用 setCharacterEncoding 方法,唯一的区别就是 setContentType 方法设置了 Content-Type 头信息

疑因3

排除掉2后,怀疑是 HttpClinet 对 ContentType 的处理有问题,并且 EntityUtils.toString(entity, Charset.forName("UTF-8"));

中的编码没有起作用。

查看其源码,如下:

Apache HttpClient 读取响应乱码问题总结
图1
Apache HttpClient 读取响应乱码问题总结
图2

从图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 的入口(笑哭),如有人知道,烦请告知,不胜感激!

上一篇:iOS案例:读取指定目录下的文件列表


下一篇:JavaScript原生DOM操作API总结