SpringMVC沒法獲取請求中的參數的問題的調查與解決(1)

*不管@RequestBody仍是@RequestParam註解同樣,都會使用全局的Encoding進行解碼,會致使特殊編碼的參數值丟失。html

只要拋棄掉註解,就徹底能夠在Controller層獲得請求的Raw數據!java

-----api

使用框架能夠節約開發時間,但有時因爲隱藏了一些實現細節,致使對底層的原理知之不詳,碰到問題時不知道該從哪個層面入手解決。所以我特地記錄了下面這個典型問題的調查和解決過程供參考。app

 

事情是這樣的,咱們原來有一個移動端調用的發表評論的API,是幾年前在NET平臺上開發的,移植到JAVA後,發現安卓版APP沒法正常發表漢字評論。框架

基於SpringMVC建立的JAVA版API接口大體以下,經調查發現,關鍵的content參數,在Controller層檢查結果爲空。工具

 

	@RequestMapping(value = "/Test.api")
	public Object test(
			HttpServletRequest request,
			HttpServletResponse response,
    		        @RequestParam(value = "content", required = false, defaultValue="") String content) {

                // 在這裏,content的值爲空   

        }

用Charles抓包檢查Post的Form數據,確實有字段content,且有漢字值。但檢查其Raw數據竟然爲這樣的形式:content=%u611f%u53d7%u4e00%u4e0b%u8d85%u4eba%u7684%u808c%u8089%uff0cui

 

咱們知道,目前java經常使用的URLEncoder類,通常將漢字轉換成"%xy"的形式,xy是兩位16進制的數值,不會出現%u後面跟4個字符這種狀況。編碼

%u開頭表明這是一種Unicode編碼格式,後面的四個字符是二字節unicode的四位16進制碼。在Charles軟件上,支持這種解碼,因此能夠正常看到抓包數據中的漢字。spa

 

可是咱們從SpringMVC框架層面統一指定了encoding爲UTF-8,根據@RequestParam註解,使用UTF-8進行content參數的解碼時,必然異常,由此致使了Post過來的content字段丟失。3d

 

和安卓團隊確認,發現過去他們確實採用了本身獨有的Encode方法對Post數據進行編碼:

    public static String UrlEncodeUnicode(final String s)
    {
        if (s == null)
        {
            return null;
        }
        final int length = s.length();
        final StringBuilder builder = new StringBuilder(length); // buffer
        for (int i = 0; i < length; i++)
        {
            final char ch = s.charAt(i);
            if ((ch & 0xff80) == 0)
            {
                if (Utils.IsSafe(ch))
                {
                    builder.append(ch);
                }
                else if (ch == ' ')
                {
                    builder.append('+');
                }
                else
                {
                    builder.append("%");
                    builder.append(Utils.IntToHex((ch >> 4) & 15));
                    builder.append(Utils.IntToHex(ch & 15));
                }
            }
            else
            {
                builder.append("%u");
                builder.append(Utils.IntToHex((ch >> 12) & 15));
                builder.append(Utils.IntToHex((ch >> 8) & 15));
                builder.append(Utils.IntToHex((ch >> 4) & 15));
                builder.append(Utils.IntToHex(ch & 15));
            }
        }
        return builder.toString();
    }

採用這種方式的緣由已經不可考證,而且安卓團隊已經決定將在將來版本中放棄該編碼方式,採用JAVA經常使用的Encoder類進行UTF-8的編碼。問題定位後,決定新版API中必需要兼容新舊兩種編碼方式。

可是目前SpringMVC的@RequestParam註解負責了請求數據的解碼,咱們從哪一層切入,截獲請求數據,判斷其編碼方式,並動態選用不一樣的解碼方式來處理呢?

通過DEBUG,以爲下面的方式是可行的。

 

解決問題的步驟:

修改API的接口形式,放棄@RequestParam註解,放棄@RequestBody註解,在Controller層直接從request的stream中獲得參數的raw數據並本身解析

    @RequestMapping(value = "/Test.api")
    public Object test(
            HttpServletRequest request,
            HttpServletResponse response) {

            String line = "";
            StringBuilder body = new StringBuilder();
            int counter = 0;
            
            InputStream stream;
            stream = request.getInputStream();
            
            //讀取POST提交的數據內容
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
            while ((line = reader.readLine()) != null) {
                if(counter > 0){
                    body.append("\r\n");
                }
                body.append(line);
                counter++;
            }
            
            //POST請求的raw數據能夠得到
            System.out.println(body.toString());

                // 使用自定義的解析工具類對body的內容進行解碼
                RequestParamMap map = new RequestParamMap(body.toString());
                ...
       }

 

只要在進入controller層以前,確保沒有別的Filter之類調用了request.getInputStream()就沒有問題,由於request.getInputStream()只能被碰一次,以後就再沒法獲取原始的請求數據了。

 

然而,接着就發現,確實有一個自定義的攔截器,須要獲取POST的InputStream數據...

http://www.cnblogs.com/csliwei/p/5557353.html

-----

相關文章
相關標籤/搜索