SpringBoot的坑】Restful请求报错Request method ‘POST‘ not supported,HiddenHttpMethodFilter无法将POST转换为PUT原因分析

直接上结论:

因为 SpringBoot 版本原因,在我目前使用的 2.2.4 版本中,需要在springapplication.xml文件中 添加配置:

spring.mvc.hiddenmethod.filter.enabled = true

什么是 REST

Restful 目的只是让 url 看起来更简洁实用,是资源状态的一种表达。
在这里插入图片描述
Restful 的使用

由于 H5 的 form 表单仅支持 POST 和 GET 两种请求,实现 restfulAPI 还需要 PUT 和 DELETE ,所以需要使用 HiddenHttpMethodFilter 组件在发送HTTP请求时对请求类型进行伪装。



    
    
    
    
    

  

前端处理
表单页面只支持get和post方式,所以页面创建一个post表单,表单里面创建一个input项,name="_method",值就是我们指定的请求方式。

后台处理
后台需要用 HiddenHttpMethodFilter,该组件 SpringBoot 已经自动配置好,无需再 @Bean 组件
错误分析

今天在学 SpringBoot 的时候遇到了由于 starter 版本 1 和 2 不同造成的小坑:

后台总是无法映射到 Controller 里对应的 PUT 请求,报错:

    后台报错:Request method ‘POST’ not supported

EmployeeController.java 的部分代码如下:

    /**
     * 员工删除
     * @param id
     * @return
     */
    @DeleteMapping("/emp/{id}") // 处理 delete 请求
    public String deleteEmployee(@PathVariable("id") Integer id) {
        System.out.println("删除员工id:" + id);
        employeeDao.delete(id);
        return "redirect:/emps";
    }

   

一开始以为是前端页面的问题,F12 看了一下 HTTP 请求头和表单提交信息:
在这里插入图片描述

看上图中显示的请求信息,提交的是post请求,表单中的_method属性值为delete,没啥问题

然后去瞄了眼 webmvc 的自动配置类 WebFluxAutoConfiguration:
(路径是org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java)
在这里插入图片描述

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

  

​发现较先前版本多了@ConditionalOnProperty的注解,也就是条件引入。查看括号内的内容可以知道,这个组件是否加入容器决定于这个属性,再看下SpringBoot配置的metadata元数据对这个property的说明:
(路径是C:\Users\Bug\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.4.RELEASE\spring-boot-autoconfigure-2.2.4.RELEASE.jar!\META-INF\spring-configuration-metadata.json)
在这里插入图片描述

{
  "name": "spring.mvc.hiddenmethod.filter.enabled",
  "type": "java.lang.Boolean",
  "description": "Whether to enable Spring's HiddenHttpMethodFilter.",
  "defaultValue": false
},

 

可以知道 SpringBoot 仅在spring.mvc.hiddenmethod.filter.enabled这个 property 的值为 true 时才会引入这个组件,但 SpringBoot 默认该属性值为 false ,所以该版本这个组件是默认没有加入容器的。

所以我们只需在配置文件里加上这一句:spring.mvc.hiddenmethod.filter.enabled = true 即可。
附:Spring 的 HiddenHttpMethodFilter 源码

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.filter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;

/**
 * {@link javax.servlet.Filter} that converts posted method parameters into HTTP methods,
 * retrievable via {@link HttpServletRequest#getMethod()}. Since browsers currently only
 * support GET and POST, a common technique - used by the Prototype library, for instance -
 * is to use a normal POST with an additional hidden form field ({@code _method})
 * to pass the "real" HTTP method along. This filter reads that parameter and changes
 * the {@link HttpServletRequestWrapper#getMethod()} return value accordingly.
 * Only {@code "PUT"}, {@code "DELETE"} and {@code "PATCH"} HTTP methods are allowed.
 *
 *

The name of the request parameter defaults to {@code _method}, but can be
 * adapted via the {@link #setMethodParam(String) methodParam} property.
 *
 *

NOTE: This filter needs to run after multipart processing in case of a multipart
 * POST request, due to its inherent need for checking a POST body parameter.

 * So typically, put a Spring {@link org.springframework.web.multipart.support.MultipartFilter}
 * before this HiddenHttpMethodFilter in your {@code web.xml} filter chain.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 */
public class HiddenHttpMethodFilter extends OncePerRequestFilter {

    private static final List ALLOWED_METHODS =
            Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
                    HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

    /** Default method parameter: {@code _method}. */
    public static final String DEFAULT_METHOD_PARAM = "_method";

    private String methodParam = DEFAULT_METHOD_PARAM;


    /**
     * Set the parameter name to look for HTTP methods.
     * @see #DEFAULT_METHOD_PARAM
     */
    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        HttpServletRequest requestToUse = request;

        if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter(requestToUse, response);
    }


    /**
     * Simple {@link HttpServletRequest} wrapper that returns the supplied method for
     * {@link HttpServletRequest#getMethod()}.
     */
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        @Override
        public String getMethod() {
            return this.method;
        }
    }

}

————————————————
版权声明:本文为CSDN博主「寒泉Hq」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_42483341/article/details/104098417

你可能感兴趣的:(Spring,spring,boot)