0
点赞
收藏
分享

微信扫一扫

Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理


一、背景

博主一直都是在做前后端分离的项目,最近在系统的学习Spring系列,发现集成在后端应用的页面中的Form表单默认并不支持REST风格;因此有了今天的故事

二、不支持REST的写法

Controller:

@RestController
public class RestDemoController {

@GetMapping("/user")
public String getUser() {
return "getUser";
}

@PostMapping("/user")
public String postUser() {
return "postUser";
}

@PutMapping("/user")
public String putUser() {
return "putUser";
}

@DeleteMapping("/user")
public String deleteUser() {
return "deleteUser";
}
}

在classpath:/static/目录下有一个user.html文件

Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理_mvc

内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
getUser:
<form action="/user" method="get">
<input value="Submit" type="submit">
</form>

postUser:
<form action="/user" method="post">
<input value="Submit" type="submit">
</form>

PutUser:
<form action="/user" method="put">
<input value="Submit" type="submit">
</form>

DeleteUser:
<form action="/user" method="delete">
<input value="Submit" type="submit">
</form>

</body>
</html>

走页面访问,我们发现:get和post类型的方法可以正常访问,二put和delete类型的方法都是访问到get方法;

Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理_后端_02

三、支持REST的写法

针对如何Form表单实现PUT和DELETE方法,Spring MVC提供了如下方式:

  • 在form表单中添加name为​​_method​​,type为​​hidden​​,value为​​put​​/​​delete​​的输入框:

<form action="/user" method="post">
<input name="_method" type="hidden" value="put">
<input value="Submit" type="submit">
</form>

  • 在后台的application.yml文件中开启识别form中"_method"名称功能

spring:
# 开启识别form中"_method"功能
mvc:
hiddenmethod:
filter:
enabled: true

user.html页面如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
getUser:
<form action="/user" method="get">
<input value="Submit" type="submit">
</form>

postUser:
<form action="/user" method="post">
<input value="Submit" type="submit">
</form>

PutUser:
<form action="/user" method="post">
<input name="_method" type="hidden" value="put">
<input value="Submit" type="submit">
</form>

DeleteUser:
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete">
<input value="Submit" type="submit">
</form>

</body>
</html>

application.yml如下:

spring:
# 开启识别form中"_method"功能
mvc:
hiddenmethod:
filter:
enabled: true

验证:可以正常访问到put/delete方法

Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理_mvc_03

四、原理(HiddenHttpMethodFilter源码解析)

为什么通过上述的配置就可以让form支持REST风格呢?

我们通过yaml文件中的配置​​spring.mvc.hiddenmethod.filter.enable​​属性往里跟一下。

Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理_后端_04


全局搜索​​spring.mvc.hiddenmethod.filter​​​,进入到​​WebMvcAutoConfiguration​​类中:

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

我们接着看new一个​​OrderedHiddenHttpMethodFilter​​其中都做了什么?

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {

/**
* The default order is high to ensure the filter is applied before Spring Security.
*/
public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;

private int order = DEFAULT_ORDER;

@Override
public int getOrder() {
return this.order;
}

/**
* Set the order for this filter.
* @param order the order to set
*/
public void setOrder(int order) {
this.order = order;
}

}

从代码上俩看,这里并没有和REST相关的实际操作,我们接着去看看​​OrderedHiddenHttpMethodFilter​​​的父类​​HiddenHttpMethodFilter​​,

​HiddenHttpMethodFilter#doFilterInternal()​​方法从命令来看应该就是过滤的核心逻辑了:

  • POST请求过来,会解析出请求中的_method属性,然后将其value值转换为大写,最后通过将请求的类型替换掉。
  • 请求的类型必须是POST,并且允许的REST方法只有PUT、DELETE、PATCH,别的方法都不会被转换,也就是说维持原样。

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

HttpServletRequest requestToUse = request;

// 只有当请求的类型是POST,并且没有异常的情况才会进入
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// this.methodParam的值就是我们在Form表单中配置的`_method`
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
// ALLOWED_METHODS表示所有允许的方法类型,目前只支持PUT、DELETE、PATCH
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}

filterChain.doFilter(requestToUse, response);
}

public static final String DEFAULT_METHOD_PARAM = "_method";

private String methodParam = DEFAULT_METHOD_PARAM;

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


举报

相关推荐

0 条评论