*不管@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
-----