Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 1006|回复: 0

Ajax 直接发送 PUT 请求,后端无法接收到数据的原因及解决方案

[复制链接]
  • TA的每日心情
    奋斗
    4 天前
  • 签到天数: 802 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726006
    发表于 2021-5-23 12:59:26 | 显示全部楼层 |阅读模式

    一、问题描述:

        使用 Ajax 直接发送 PUT 请求,但 Spring MVC 封装的对象中,除过 URI 中带有的 id 字段被成功封装,请求体中的数据没有被封装到对象中。

        通过测试,前端传来的请求体中有数据;通过 HttpServletRequest 对象,使用 request.getParameter() 方法却也获取不到数据

    image

    二、解决方案:

        在 web.xml 中添加 HttpPutFormContentFilter 过滤器,原理向下看:

      1 <filter>
      2     <filter-name>httpPutFormContentFilter</filter-name>
      3     <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
      4 </filter>
      5 <filter-mapping>
      6     <filter-name>httpPutFormContentFilter</filter-name>
      7     <url-pattern>/*</url-pattern>
      8 </filter-mapping>

    三、原因分析:

    1. Tomcat 会将请求体中的数据封装成一个 Map,调用 request.getParameter() 会从这个 Map 中取值,Spring MVC 封装 POJO 对象时,对于对象中的每个属性值也会调用 request.getParameter() 方法去获取,然后赋值给对象相应的属性,完成属性封装。

    2. 但是,对于 PUT 请求,Tomcat 不会封装请求体中的数据为一个 Map,只会将 POST 请求的请求体中数据封装成 Map;这样的话,无论是直接调用 request.getParameter() 方法,还是 Spring MVC 封装对象,肯定都拿不到属性值了。

    四、源码分析:

    1. 在 Tomcat 源码中 org.apache.catalina.connector 包的 Request.java 类中 parseParameters() 方法用来解析请求参数,方法先会进行一系列设置和判断,然后再解析请求参数。其中,下面这段代码就会判断请求类型,如果符合 if 条件就会直接返回,也就是判断 parseBodyMethod 中是不是包含当前的请求方式,如果包含,Tomcat 服务器就继续处理解析请求体中的参数,封装到 Map 中;如果不包含,就直接返回,而不再执行下面的方法解析参数,那么 Map 中也就没有相应的参数了。

      1 if( !getConnector().isParseBodyMethod(getMethod()) ) {
      2     success = true;
      3     return;
      4 }

    2. getConnector() 方法用来获取当前连接对象,然后调用 isParseBodyMethod() 方法判断 parseBodyMethodSet 集合中是不是包含传进来的 method 的值,这个 method 值就是当前的请求方式。

      1 /**
     2  * Request.java类中
     3  * 获取当前的请求方式
     4  */
      5 public String getMethod() {
      6     return coyoteRequest.method().toString();
      7 }
      8 
      9 /**
     10  * Connector.java类中
     11  */
     12 protected boolean isParseBodyMethod(String method) {
     13      return parseBodyMethodsSet.contains(method);
     14 }
    

    3. parseBodyMethodSet 集合中一般使用的都是默认值,也就是相当于将 parseBodyMethods 的值赋给了 parseBodyMethodSet,而 parseBodyMethods 的值默认是 post。

      1 /**
     2   * 调用 setParseBodyMethods 将 getParseBodyMethods() 获取的 parseBodyMethods 的值赋值给
     3   * parseBodyMethods
     4   */
      5 protected void initInternal() throws LifecycleException {
      6 
      7     // ...
      8 
      9     // Make sure parseBodyMethodsSet has a default
     10     if( null == parseBodyMethodsSet ) {
     11         setParseBodyMethods(getParseBodyMethods());
     12     }
     13 
     14     // ...
     15 }
     16 
     17 /**
     18  * 获取 parseBodyMethods 的值
     19  */
     20 protected String parseBodyMethods = "POST";
     21 
     22 public String getParseBodyMethods() {
     23     return this.parseBodyMethods;
     24 }
     25 
     26 /**
     27  * 定义连接器的规则,如果连接器允许解析非POST请求的请求体,就传入规则,默认都是没有的
     28  * 所以 parseBodyMethodSet 使用的是默认值
     29  * 将 methods 的值赋值给 parseBodyMethods
     30  * 将 methodSet 的值赋值给 parseBodyMethodsSet
     31  */
     32 public void setParseBodyMethods(String methods) {
     33 
     34     HashSet<String> methodSet = new HashSet<String>();
     35 
     36     if( null != methods ) {
     37         methodSet.addAll(Arrays.asList(methods.split("\\s*,\\s*")));
     38     }
     39 
     40     // ...
     41 
     42     this.parseBodyMethods = methods;
     43     this.parseBodyMethodsSet = methodSet;
     44 }

    4. 通过前面几步分析,parseBodyMethodSet 中默认只有 post,也只有当前请求方式是 post 的时候才会解析参数,因此,不管是直接通过 Request 对象的 getParameter() 还是 Spring MVC 封装 POJO 对象都不会获取到参数值。

    五、HttpPutFormContentFilter 过滤器原理

      1 /**
     2  * 封装请求体数据,重新包装 Request 对象
     3  */
      4 protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse 	response, FilterChain filterChain) throws ServletException, IOException {
      5     // 当是 put 请求或 patch 请求的时候
      6     if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && this.isFormContentType(request)) {
      7         HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
      8             // 获取请求体中的数据流,封装成HttpInputMessage
      9             public InputStream getBody() throws IOException {
     10                 return request.getInputStream();
     11             }
     12         };
     13 
     14         // 将上一步封装的 HttpInputMessage 读取封装成一个MultiValueMap 对象,
     15         // 这个对象继承自 Map
     16         // 将请求体中的数据封装成 Map
     17         MultiValueMap<String, String> formParameters = this.formConverter.read((Class)null, inputMessage);
     18 
     19         // 使用 HttpPutFormContentRequestWrapper 重新包装 Request 对象
     20         HttpServletRequest wrapper = new HttpPutFormContentFilter.HttpPutFormContentRequestWrapper(request, formParameters);
     21         filterChain.doFilter(wrapper, response);
     22     } else {
     23         filterChain.doFilter(request, response);
     24     }
     25 }
     26 
     27 /**
     28  * 包装 Request 对象具体实现
     29  * 重写父类的 getParameter() 等方法,先调用父类的方法
     30  * 如果能获取到就是用父类获取的;
     31  * 如果获取不到,就是用当前类获取的。
     32  */
     33 private static class HttpPutFormContentRequestWrapper extends 	HttpServletRequestWrapper {
     34       private MultiValueMap<String, String> formParameters;
     35 
     36       public String getParameter(String name) {
     37              String queryStringValue = super.getParameter(name);
     38              String formValue = (String)this.formParameters.getFirst(name);
     39              return queryStringValue != null ? queryStringValue : formValue;
     40       }
     41 
     42       // ...
     43 }
    
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-11-15 14:46 , Processed in 0.187689 second(s), 28 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表