本篇和RESTFul与RESTFul案例这篇文章有很多重复的地方,就当是复习吧。
表单提交发送PUT和DELETE请求
REST(Representational State Transfer,表述性状态转移)是一种架构风格,它使用HTTP请求方式动词表示对资源的操作。
具体看下面的例子吧。
- 新建Spring项目:demo3。
新建时添加3个依赖:Lombok、Spring Configuration Processor和SpringWeb。
项目新建好后,自动生成的pom.xml如下所示。
另外,将自动生成的配置文件application.properties重命名为application.yml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo3</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- com.example.boot下新建控制器类controller.Demo3Controller。
package com.example.boot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Demo3Controller {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String get(){
return "get user";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String post(){
return "add user";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String put(){
return "update user";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String delete(){
return "delete user";
}
}
- resources.static下新建欢迎页面index.html。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form action="/user" method="get">
<input type="submit" value="REST-GET"/>
</form>
<form action="/user" method="post">
<input type="submit" value="REST-POST"/>
</form>
<form action="/user" method="put">
<input type="submit" value="REST-PUT"/>
</form>
<form action="/user" method="delete">
<input type="submit" value="REST-DELETE"/>
</form>
</body>
</html>
- 启动应用,访问localhost:8080。
点击按钮REST-GET,表单method设置为get,返回“查询用户”,OK;
点击按钮REST-POST,表单method设置为post,返回“新增用户”,OK;
点击按钮REST-PUT,表单method设置为put,返回“新增用户”,NOK;
点击按钮REST-POST,表单method设置为delete,返回“新增用户”,NOK。
由于表单method仅支持get和post方法,如果method设置为其他请求方式,如本例中的put或delete,浏览器会当作get请求方式处理。
要想使用表单发送put或delete请求,需要借助过滤器HiddenHttpMethodFilter
,其源码如下,
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
做好以下两点就行:
1)表单的method设置为post
。
<form method="post"></form>
2)发送给服务器的数据必须包括名为_method
,值为put
或delete
的这么一个数据,可用input[type=“hidden”]实现。
<input type="hidden" name="_method" value="put"/>
按照以上思路修改index.html,修改后的结果如下,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form action="/user" method="get">
<input type="submit" value="REST-GET"/>
</form>
<form action="/user" method="post">
<input type="submit" value="REST-POST"/>
</form>
<form action="/user" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="submit" value="REST-PUT"/>
</form>
<form action="/user" method="post">
<input type="submit" value="REST-DELETE"/>
</form>
</body>
</html>
另外,还有很重要的一点,那就是需要在配置文件application.yml中添加以下内容,以启用HiddenHttpMethodFilter。
spring:
mvc:
hiddenmethod:
filter:
enabled: true
想知道为啥,到spring-boot-autoconfigure-2.6.2.jar的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration里看看了。
public class WebMvcAutoConfiguration {
public WebMvcAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//...
}
需要满足两个条件,OrderedHiddenHttpMethodFilter实例才会添加到容器中 。
1)@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
,条件:类路径下有HiddenHttpMethodFilter。
Ctrl+N,输入OrderedHiddenHttpMethodFilter,可找到,所以满足条件。
2)
@ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"} )
,条件:配置文件中需要配置spring.mvc.hiddenmthod.filter.enable,且为true或者on。
最后,重启应用,查看效果。
刚刚提到,表单要发送put或delete请求,发送给服务器的数据必须包括名为_method,值为put或delete的这么一个数据,通常用<input type="hidden" name="_method" value="delete"/>
来实现。其实,不一定非得name="_method"
,也可以是name="_mymethod
或者其他,也就是说,可以自定义。具体怎么自定义,还得回到HiddenHttpMethodFilter的源码。
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
//...
private String methodParam = "_method";
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
//...
}
在com.example.boot下新建配置类config.MyConfig,如下,
package com.example.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration
public class MyConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_mymethod");
return hiddenHttpMethodFilter;
}
}
同时修改index.html,将name="_method"
修改为name=_mymethod
,如下。
<form action="/user" method="post">
<input type="hidden" name="_mymethod" value="put"/>
<input type="submit" value="REST-PUT"/>
</form>
<form action="/user" method="post">
<input type="hidden" name="_mymethod" value="delete"/>
<input type="submit" value="REST-DELETE"/>
</form>
重启应用,访问localhost:8080,验证结果,符合预期。
IDEA模拟HTTP请求
使用IDEA发送HTTP请求,不需要使用HiddenHttpMethodFilter,不信瞧瞧。
首先修改application.yml把HiddenHttpMethodFilter关闭,如下。
spring:
mvc:
hiddenmethod:
filter:
enabled: off
然后,在demo3项目根目录下新建目录http,在http下新建文件demo3.http,内容如下。
GET http://localhost:8080/user
Content-Type: application/json
###
POST http://localhost:8080/user
Content-Type: application/json
###
PUT http://localhost:8080/user
Content-Type: application/json
###
DELETE http://localhost:8080/user
Content-Type: application/json
最后,重启应用,发送请求。