1. 程式人生 > >Apache HttpClient 讀取響應亂碼問題總結

Apache HttpClient 讀取響應亂碼問題總結

prev else tle hit msu except 版本 Coding 兼容

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() 方法的處理不一致,遂查看源碼,如下:

技術分享圖片
enter description here

發現 setContentType 方法內部也會調用 setCharacterEncoding 方法,唯一的區別就是 setContentType 方法設置了 Content-Type 頭信息

疑因3

排除掉2後,懷疑是 HttpClinet 對 ContentType 的處理有問題,並且 EntityUtils.toString(entity, Charset.forName("UTF-8"));
中的編碼沒有起作用。

查看其源碼,如下:

技術分享圖片
圖1

技術分享圖片
圖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 的入口(笑哭),如有人知道,煩請告知,不勝感激!

Apache HttpClient 讀取響應亂碼問題總結