【问题标题】:Passing data in the body of a DELETE request在 DELETE 请求的正文中传递数据
【发布时间】:2014-08-19 02:56:14
【问题描述】:

我有两个 Spring MVC 控制器方法。两者都在请求正文中接收相同的数据(采用 HTLM POST 表单的格式:version=3&name=product1&id=2),但一种方法处理 PUT 请求,另一种方法处理 DELETE

@RequestMapping(value = "ajax/products/{id}", method = RequestMethod.PUT)
@ResponseBody
public MyResponse updateProduct(Product product, @PathVariable("id") int productId) {

//...
}

@RequestMapping(value = "ajax/products/{id}", method = RequestMethod.DELETE)
@ResponseBody
public MyResponse updateProduct(Product product, @PathVariable("id") int productId) {

//...
}

在第一种方法中,product 参数的所有字段都正确初始化。在第二个中,只有id 字段被初始化。其他字段为 null 或 0。(id 可能由于 id 路径变量而被初始化)。

我可以看到 HttpServletRequest 对象包含请求正文 (version=3&name=product1&id=2) 中所有字段的值。它们只是没有映射到product 参数的字段。

我怎样才能使第二种方法起作用?

我也尝试使用@RequestParam 注释参数。在处理PUT 请求的方法中,它可以工作。在DELETE 方法中,我得到一个异常:org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'version' is not present

我需要在 DELETE 请求的正文中传递数据,因为数据包含用于乐观锁定的行版本。

【问题讨论】:

  • 你在使用Tomcat吗?

标签: spring spring-mvc


【解决方案1】:

问题不是Spring 问题,而是Tomcat 问题。

默认情况下,当 HTTP 方法为 POST 时,Tomcat 只会解析表单样式的参数(至少对于我检查的 7.0.54 版本,但对于所有 Tomcat 7 版本可能都相同)。

为了能够处理 DELETE 方法,您需要设置 Tomcat 连接器的 parseBodyMethods 属性。连接器配置在server.xml完成。

您更新后的连接器很可能如下所示:

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000"
           redirectPort="8443"
           parseBodyMethods="POST,PUT,DELETE"
           URIEncoding="UTF-8" />

Here 是配置 Tomcat 连接器的文档页面。

一旦你设置 Tomcat 来解析参数,Spring 就可以正常工作(尽管在你的情况下,你可能需要从控制器方法中删除 @RequestBody

【讨论】:

  • 谢谢!它解释了我的问题。当我再获得 3 点声望时,我会支持这个答案。但我宁愿直接从HttpServletRequest 中拉出version,也不愿弄乱Tomcat 配置文件。
  • 没问题!问题是您也无法从HttpServletRequest.getParameter 中提取version,因为Tomcat 从未使用该信息填充必要的数据结构。
  • 另外更改server.xml 中的连接器真的没什么大不了的。您可能想要做的就是添加parseBodyMethods="POST,PUT,DELETE"(或者如果它已经存在则更改它)
  • 我目前正在使用request.getInputStream()方法进行调试。不是一个优雅的解决方案,但它有效:)
  • 最后我使用了如下解决方案:我在控制器方法中添加了@RequestBody MultiValueMap&lt;String, String&gt; formData参数,然后得到了version字段:String version = formData.get("version").get(0)。为此,应注册FormHttpMessageConverter。如果使用 Java Config 或 &lt;mvc:annotation-driven /&gt; 标签,则默认注册此消息转换器。我会等一下其他人的cmets,如果没有人说新的话,我会接受这个答案。
【解决方案2】:

您可以尝试将注释 @RequestBody 添加到您的 Product 参数中。

但如果只需要传递版本信息,使用请求参数更合适。

所以在你的删除方法中添加一个新参数@RequestParam("version") int version,并在调用删除方法时传递一个查询参数,如..ajax/products/123?version=1

正如你所说,请求参数在删除时对你不起作用,你能发布你使用的确切网址和方法签名吗?

【讨论】:

  • 我无法在 URL 中传递版本,因为我必须让我的 URL 保持 RESTful。我的请求正文如下所示:version=3&amp;name=product1&amp;id=2(我认为这是 POST HTML 表单的格式)。
  • 在我的PUTPOST 控制器方法中我不使用@RequestBody 是有原因的:不使用@RequestBody 使得处理数据绑定错误更加简单(您可以处理它们与验证错误相同)。例如,数据绑定错误是在将字符串放入整数字段时发生的错误。
  • 如果我理解正确的话,@RequestBody 会使用“消息转换器”将传入的表单绑定到product 对象,任何数据绑定错误都会导致难以处理的异常(至少在您使用 @RequestBody 处理 JSON 数据时是这种情况)。另一方面,我不需要担心DELETE方法的数据绑定错误,所以我会尝试使用@RequestBody
  • +1 用于建议 @RequestBody 注释。但是,似乎没有可以将application/x-www-form-urlencoded 表单转换为产品对象的消息转换器(我发现只有MultiValueMap&lt;String, String&gt; 的消息转换器)。所以,我认为,要让@RequestBody Product product 工作,我必须编写自己的消息转换器。
【解决方案3】:

春季启动 1.5.*

@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
    return new TomcatEmbeddedServletContainerFactory(){
        @Override
        protected void customizeConnector(Connector connector) {
            super.customizeConnector(connector);
            connector.setParseBodyMethods("POST,PUT,DELETE");
        }
    };
}

【讨论】:

    【解决方案4】:

    在 DELETE 请求的正文中传递数据

    @组件 公共类 CustomiseTomcat 实现 WebServerFactoryCustomizer {

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
    
        factory.addConnectorCustomizers( new TomcatConnectorCustomizer() {
            @Override
            public void customize(Connector connector) {
                connector.setParseBodyMethods("POST,PUT,DELETE");
            }
        });
    }
    

    }

    【讨论】:

      【解决方案5】:

      对于 Spring Boot 2.0+:

      @Bean
      public TomcatServletWebServerFactory containerFactory() {
          return new TomcatServletWebServerFactory() {
              @Override
              protected void customizeConnector(Connector connector) {
                  super.customizeConnector(connector);
                  connector.setParseBodyMethods("POST,PUT,DELETE");
              }
          };
      }
      

      【讨论】:

      • 嗨,java_pickle,欢迎来到 Stack Overflow。对答案的一些解释是非常可观的。这将有助于理解代码的目标。编码愉快!
      猜你喜欢
      • 2013-09-16
      • 2017-05-04
      • 1970-01-01
      • 2013-02-11
      • 2013-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多