zoukankan      html  css  js  c++  java
  • SpringMVC无法获取请求中的参数的问题的调查与解决(1)

    *无论@RequestBody还是@RequestParam注解一样,都会使用全局的Encoding进行解码,会导致特殊编码的参数值丢失。

    只要抛弃掉注解,就完全可以在Controller层得到请求的Raw数据!

    -----

    使用框架可以节约开发时间,但有时由于隐藏了一些实现细节,导致对底层的原理知之不详,碰到问题时不知道该从哪一个层面入手解决。因此我特意记录了下面这个典型问题的调查和解决过程供参考。

    事情是这样的,我们原来有一个移动端调用的发表评论的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%uff0c

    我们知道,目前java常用的URLEncoder类,一般将汉字转换成"%xy"的形式,xy是两位16进制的数值,不会出现%u后面跟4个字符这种情况。

    %u开头代表这是一种Unicode编码格式,后面的四个字符是二字节unicode的四位16进制码。在Charles软件上,支持这种解码,所以可以正常看到抓包数据中的汉字。

    但是我们从SpringMVC框架层面统一指定了encoding为UTF-8,根据@RequestParam注解,使用UTF-8进行content参数的解码时,必然异常,由此导致了Post过来的content字段丢失。

    和安卓团队确认,发现过去他们确实采用了自己独有的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("
    ");
                    }
                    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

    -----

  • 相关阅读:
    python基础之元组、文件操作、编码、函数、变量
    python---基础之模块,列表,元组,字典
    python成长之路-----day1-----作业(登录程序和三级菜单)
    k8s的port、targetport、nodeport之间的区别
    查找并删除文件
    systemctl自定义service
    中标麒麟7.0源
    springboot问题,没有主清单属性
    iso搭建本地源
    添加路由
  • 原文地址:https://www.cnblogs.com/csliwei/p/5526281.html
Copyright © 2011-2022 走看看