zoukankan      html  css  js  c++  java
  • Spring MVC的Post请求参数中文乱码的原因&处理

    一、项目配置:

    1. Spring 4.4.1-RELEASE
    2. Jetty 9.3.5
    3. JDK 1.8
    4. Servlet 3.1.0
    5. web.xml文件中没有配置编解码Filter

    二、实际遇到的问题:
    客户端(比如java)发送post请求访问接口,数据放在body里面,每个参数utf-8编码。
    从body里面取出的中文参数是乱码。


    下面是发送请求的代码和服务端接收请求的代码。

    • 客户端代码。
      这是一个真实的第三方访问API的案例,这段代码请求到PHP系统正常,请求到java系统就会出现乱码。
      但是中文参数放到URL中解码正常,放到请求体中就是乱码。
      通过httpclient4.1发送Post请求如下:

      public static void postData(String sign, String timestamp) {    // 创建默认的httpClient实例.    
        CloseableHttpClient httpclient=null;   
        String result="";    
        try {        
            httpclient = HttpClients.createDefault();        
            String url = "http://example/api/entry";            
            HttpPost httpPost = new HttpPost(url);        //设置请求和传输超时时间        
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(6000).setConnectTimeout(6000).build();        
            httpPost.setConfig(requestConfig);        
            MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);        
            entity.addPart("app_id", new StringBody("c5eb3ba8c0e7326559", Charset.forName("utf-8")));        
            entity.addPart("method", new StringBody("kdt.item.add", Charset.forName("utf-8")));        
            entity.addPart("timestamp", new StringBody(timestamp));        
            entity.addPart("format", new StringBody("json", Charset.forName("utf-8")));        
            entity.addPart("v", new StringBody("1.0", Charset.forName("utf-8")));        
            entity.addPart("sign", new StringBody(sign, Charset.forName("utf-8")));        
            entity.addPart("sign_method", new StringBody("md5", Charset.forName("utf-8")));        
            entity.addPart("cid", new StringBody("5000000", Charset.forName("utf-8")));        
            entity.addPart("tag_ids", new StringBody("0", Charset.forName("utf-8")));        
            entity.addPart("price", new StringBody("0.01", Charset.forName("utf-8")));        
            entity.addPart("title", new StringBody("测试", Charset.forName("utf-8")));        
            entity.addPart("desc", new StringBody("test1", Charset.forName("utf-8")));        //是否是虚拟商品。0为否,1为是。目前不支持虚拟商品        
            entity.addPart("is_virtual", new StringBody("0", Charset.forName("utf-8")));        
            entity.addPart("post_fee", new StringBody("0.0", Charset.forName("utf-8")));        //Sku的属性串。格式:pText:vText;pText:vText,多个sku之间用逗号分隔,如:颜色:黄色;尺寸:M,颜色:黄色;尺寸:S。pText和vText文本中不可以存在冒号和分号以及逗号        
            entity.addPart("sku_properties", new StringBody("color:white", Charset.forName("utf-8")));        
            entity.addPart("sku_quantities", new StringBody("998,999", Charset.forName("utf-8")));        
            entity.addPart("sku_prices", new StringBody("0.01,0.02", Charset.forName("utf-8")));        
            entity.addPart("sku_outer_ids", new StringBody("null,null", Charset.forName("utf-8")));        //该商品的外部购买地址。当用户购买环境不支持微信或微博支付时会跳转到此地址        
            entity.addPart("buy_url", new StringBody("http://img.cdn.sb.hongware.com/1461836641703511.gif", Charset.forName("utf-8")));        
            entity.addPart("quantity", new StringBody("1998", Charset.forName("utf-8")));        //宝贝修改的时候需要这个参数        
            httpPost.setEntity(entity);        
            CloseableHttpResponse response = httpclient.execute(httpPost);       
            try {            
                  HttpEntity httpEntity = response.getEntity();            
                  System.out.println(httpEntity.getContent());            
                  InputStream content = httpEntity.getContent();            
                  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content));            
                  String line;            
                  while ( (line=bufferedReader.readLine()) != null) {                
                      System.out.println(line);            
                  }        
            } catch (Exception e) {            
                  e.printStackTrace();        
            } finally {            
                  response.close();        
            }    
        } catch (Exception e) {        
            e.printStackTrace();    
        } finally {        
            try {            
                httpclient.close();        
            } catch (Exception e) {            
                // TODO Auto-generated catch block            
                e.printStackTrace();        
            }    
        }
      }
    • 服务端代码如下
      为了简化演示,我把参数提取代码Map<String, String[]> parameterMap = request.getParameterMap()冗余在这个入口函数中,以便说明问题:

      @RequestMapping(value = "/api/entry", produces = "application/json;charset=utf-8")
      public DeferredResult<Object> sign(HttpServletRequest request,HttpServletResponse response) {    
          DeferredResult<Object> deferredResult = new DeferredResult<>();    
          Map<String, String[]> parameterMap = request.getParameterMap();  
          String method = request.getParameter("method");    
          if (StringUtils.isEmpty(method)) {        
            ResponseEntity<String> responseEntity = new ResponseEntity<String>(                String.format(Constants.ERROR_RESPONSE, 50000, "service or method is null"),                HttpStatus.valueOf(200));        
            deferredResult.setResult(responseEntity);        
            return deferredResult;    
          }    
          int lastIndex = method.lastIndexOf(".");    
          String service = method.substring(0, lastIndex);    
          method = method.substring(lastIndex + 1);    
          event.setService(service);    
          event.setMethod(method);    
          event.setResult(deferredResult);    
          proxy.doAction(request,response,event);    
          return deferredResult;
      }

    服务端通过Map<String, String[]> parameterMap = request.getParameterMap()取出所有参数,传进来title参数是乱码!!

    三、根本原因
    Servlet 3.0规范中有关请求数据编码的解释如下:

    当前很多浏览器并不发送带Content-Type头部的字符编码标识符,它会把字符编码的决定留在读取HTTP请求的时候。如果客户端没有指明编码,容器用来创建请求读和解析POST数据的默认编码必须是"ISO-8859-1"。然而,为了提示开发者客户端没有成功发送一个字符编码,容器中getCharacterEncoding方法会返回null。
    如果客户端没有设置字符编码,并且请求数据使用了不同编码而不是上述的默认编码,程序将会出现中断。为了纠正这种状态,一个新的方法setCharacterEncoding(String enc) 被添加到ServletRequest接口。开发者调用这个方法能重写容器提供的字符编码。这个方法必须在解析request中任何post数据或者读任何输入之前调用。一旦数据已经被读取,调用这个方法不会影响它的编码。

    另外一种相同的解释:


    网络上同样含义的解释

    四、3种解决方法

    1. 在web.xml中配置编解码Filter
       <filter>    
         <filter-name>encodingFilter</filter-name>    
         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>    
          <init-param>        
             <param-name>encoding</param-name>        
             <param-value>UTF-8</param-value>    
          </init-param>    
          <init-param>        
             <param-name>forceEncoding</param-name>        
             <param-value>true</param-value>    
          </init-param>    
          <async-supported>true</async-supported>
       </filter>
       <filter-mapping>    
           <filter-name>encodingFilter</filter-name>    
           <url-pattern>/*</url-pattern>
       </filter-mapping>
      关于这段配置需要强调两点:
      • web.xml中,这段配置要放在所有filter的最前面,否则会不生效,根本原因请见上述第三点的解释。
      • 两个初始化参数的作用,其实看这个Filter的源码就一目了然,这两个参数是用来决定是否要设置request和response中的编码。源码很简洁:
         public class CharacterEncodingFilter extends OncePerRequestFilter {    
             private String encoding;    
             private boolean forceEncoding = false;    
             public CharacterEncodingFilter() { }    
             public void setEncoding(String encoding) {        
                 this.encoding = encoding;    
             }    
             public void setForceEncoding(boolean forceEncoding) {        
                 this.forceEncoding = forceEncoding;    
             }    
             protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {        
                 if(this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {            
                     request.setCharacterEncoding(this.encoding);            
                     if(this.forceEncoding) {                
                         response.setCharacterEncoding(this.encoding);            
                     }        
                  }        
                  filterChain.doFilter(request, response);    
             }
         }
    2. 设置Content-Type
      如果post请求方式是x-www-form-urlencoded,那么设置如下:
      Content-Type=application/x-www-form-urlencoded;charset=utf-8
      这样通过request对象取body体里面的中文是正常的。
      这种方式有一点需要注意: 如果请求方式是multipart/form-data,如上设置会导致request取不到参数。Content-Type要与传递数据匹配(本文data)
    3. 手动编解码
      比如参数title="测试",这样取出来就是"测试"。
       String str = new String(request.getParameter("title").getBytes("iso-8859-1"), "utf-8");

    综上所有,最优雅的方式是第一种解决方案--通过框架的Filter去处理。
    你仅专注于业务代码就好。

    参考资料

      1. ajax post data获取不到数据
      2. Servlet 3.0规范
      3. HTTP Content-Type常用对照表
      4. Spring官网--Consumable Media Types章节
      5. ISO-8859-1
      6. ISO-8859-1为何能显示中文
      7. 字符编码
      8. Media Type
  • 相关阅读:
    事务传播机制,搞懂。
    洛谷 P1553 数字反转(升级版) 题解
    洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is Here 题解
    洛谷 P1055 ISBN号码 题解
    洛谷 P2141 珠心算测验 题解
    洛谷 P1047 校门外的树 题解
    洛谷 P1980 计数问题 题解
    洛谷 P1008 三连击 题解
    HDU 1013 题解
    HDU 1012 题解
  • 原文地址:https://www.cnblogs.com/frankyou/p/7097468.html
Copyright © 2011-2022 走看看