SPRING 八月 22, 2021

HTML <form> 只支持 GET 和 POST!

文章字数 5.1k 阅读约需 5 mins. 阅读次数

HTML form method

在 Web 开发中,常规的提交数据方式为使用 form 表单,例如:

<form action="" method="get" class="form-example">
  <div class="form-example">
    <label for="name">Enter your name: </label>
    <input type="text" name="name" id="name" required>
  </div>
  <div class="form-example">
    <label for="email">Enter your email: </label>
    <input type="email" name="email" id="email" required>
  </div>
  <div class="form-example">
    <input type="submit" value="Subscribe!">
  </div>
</form>

form 中各属性的值,可参考 MDN 文档,其中,method 属性支持的值如下:

method

The HTTP method to submit the form with. Possible (case insensitive) values:

  • post: The POST method; form data sent as the request body.
  • get: The GET method; form data appended to the action URL with a ? separator. Use this method when the form has no side-effects.
  • dialog: When the form is inside a <dialog>, closes the dialog on submission.

这在 JSP 时代通常只使用 getpost 两种 method 时并无大碍,但在 RESTful 盛行的今日,无疑显得很不和谐。

Spring 的解决方式

在 Spring 提供的 form 标签库 中,对 HTML 中的 form 进行了全面的丰富,在 JSP 页面中引入 taglib:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

之后可以通过 <form:form> 的方式使用,如:

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

<form:form> 最终也会被服务端渲染为 <form>,那为什么这里的 method 不再局限于 HTML 规范中要求的值呢?

HiddenHttpMethodFilter

在 Spring 中,使用了一种 HTTP Method 转换方式:以上面代码片段为例,实际提交的表单的 method 是 POST 类型的,同时在请求参数中包含了一个隐藏的属性,默认为 _method,里面存放了真正的 method —— DELETE

在服务端,接收请求时,有一个 HiddenHttpMethodFilter,负责将请求的 method 修改为隐藏属性中的实际 method,再传入后续处理流程,以使得我们的 Controller 能够使用 @DeleteMapping 接收到请求。

@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);
}

一切都还挺美好的,直到……

上传文件为空

Spring 中,需要上传文件时,又同时使用了 HiddenHttpMethodFilter,如果配置不当,大概率会遇到 Controller 中接到的 MultipartFile 为空的情况。在 HiddenHttpMethodFilter源码 中,其实已经说明了这个问题:

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 org.springframework.web.multipart.support.MultipartFilter before this HiddenHttpMethodFilter in your web.xml filter chain.

只需要让处理上传文件的 Filter 先于处理 HTTP method 隐藏属性的 Filter 即可。

0%