使用框架雖能提升開發效率,但隱藏底層細節時,遇到問題往往無從下手。本文記錄一個典型案例:Android 客戶端發表中文評論失敗的排查全過程,供參考。
原 .NET API 移植至 Java SpringMVC 後,Android APP 無法正常發表中文評論。Controller 以 @RequestParam 綁定 content 參數,卻始終為空:
@RequestMapping(value = "/Test.api")
public Object test(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "content", required = false, defaultValue="") String content) {
// content 值為空
}
用 Charles 抓包發現:Form Data 中確有 content 欄位且含中文,但 Raw Data 卻是 content=%u611f%u53d7%u4e00%u4e0b%u8d85%u4eba%u7684%u808c%u8089%uff0c。標準 URLEncoder 將漢字編碼為 %xy(兩位十六進位),而此處為 %u 開頭後接四位十六進位,屬 Unicode 編碼格式。Charles 可自動解碼顯示中文,但 SpringMVC 統一指定 UTF-8 解碼,導致異常而丟棄參數。
Android 團隊確認過去使用自定義 UrlEncodeUnicode 方法編碼 POST 數據:
public static String UrlEncodeUnicode(final String s) {
// ... 逐字元處理,ASCII 走標準 %xy,非 ASCII 走 %uXXXX
}
棄用計畫已定,但新版 API 需兼容新舊兩種編碼。
@RequestParam 在框架層完成解碼,需在更早層面攔截原始數據、判斷編碼、動態解碼。經 DEBUG 確認兩條可行路徑:
@RequestMapping(value = "/Test.api")
public Object test(HttpServletRequest request, HttpServletResponse response, @RequestBody String body) {
// 獲得 raw 數據:Id=185904&content=%u611f%u53d7%u4e00%u4e0b
// 自行解析、判斷編碼、解碼
}
優點:不需修改 web.xml,改動最小;缺點:需自行實現表單解析邏輯。
public class MyFilter implements Filter {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
InputStream stream = servletRequest.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
// 解析 body,判斷編碼,解碼,將參數 setAttribute 回 request
// 注意:getInputStream() 只能讀取一次!後續 getParameterMap() 等將失效
chain.doFilter(request, response);
}
}
需在 web.xml 配置 Filter,且 getInputStream() 為一次性操作,若 Filter 中讀取,後續 Controller 將無法再獲取參數,必須在 Filter 中完成所有參數解析並存回 request 屬性。
ServletRequest.getInputStream() 僅能調用一次,讀取後流關閉,後續任何嘗試獲取參數(getParameter、getParameterMap)均會失效。