目录
1.什么是rest风格
HTTP
协议发展的过程中,提出了很
多的规则,但是这些规则有
些烦琐,于是又提出了一种风
格约定,它便是rest
风格
。在
现今流行的微服务中,这样的风格甚
至被推荐为各
个微服
务系统之间用于交互的方式。首先在
REST
风格
中,
每一个资源都只是对应着一个
网址,而这个网址
应该是一个名词 ,不存在动词
,
这代表对
资源的操作。
核心是三个名词:资源,表现层,状态转换
资源:
它可以是系统权限用户、角色和菜单
,也可以
些媒体类型 如文本、图片、歌曲等
。
表现层:有了资源还需要确定
如何表现这个
资源。
例如一
个用
户可以用
JSON,
XML
或者其他
的形式表现出来
。
状态转换:
现实中的资源并不是一成不变的,一个资源可以经历增删改查的过程。
- 使用HTTP动词表示增删改查资源, GET:查询,POST:新增,PUT:更新,DELETE:删除
- URI使用名词而不是动词,且推荐使用复数
- 保证HEAD和GET方法是安全的,不会对资源状态有所改变(污染)。比如:严格杜绝如下情况:GET/deleteProduct?id=1
- 资源的地址推荐用嵌套结构。比如:GET/friends/10375923/profile
- 警惕返回结果的大小。如果过大,及时进行分页(pagination)或者加入限制(limit)。HTTP协议支持分页(Pagination)操作,在Header中使用Link即可。
- 使用正确的HTTP Status Code 表示访问状态。
- 返回结果必须使用JSON
- HTTP状态码,在REST中都有特定的意义:200,201,202,204,400,401,403,500。比如401表示用户身份认证失败,403表示你验证身份通过了,但这个资源你不能操作。
- 如果出现错误,返回一个错误码。
参考:
https://blog.csdn.net/ayqy42602/article/details/107689748
2.初步假设测试
先写个测试控制器
@RestController
public class HelloController {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "get-zs";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "post-zs";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "delete-zs";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "put-zs";
}
}
再写个前端页面测试请求,想当然会对应匹配跳转
<body>
<h1>表单提交</h1>
<!-- 表单提交用户信息,注意字段的设置,直接是*{} -->
<form action="/user" method="get">
<input value="rest get提交" type="submit" />
</form>
<form action="/user" method="post">
<input value="rest post提交" type="submit" />
</form>
<form action="/user" method="put">
<input value="rest put提交" type="submit" />
</form>
<form action="/user" method="delete">
<input value="rest delete提交" type="submit" />
</form>
</body>
但是很可惜,结果出乎意料,如下
put请求和delete请求没有如所想的跳到对应的put页面和delete页面,按住页面中那个method属性进去可以看见method只有两个属性(分别是get和post),
可见浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持
3.源码探究及解决
spring3.0添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求,该过滤器为HiddenHttpMethodFilter。
web请求分配映射这些功能原来都是springmvc管的,现在被spring boot集成后,就要首先研究WebMvcAutoConfiguration这个自动配置类,进入后可发现类似这个功能的过滤器
@ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常,以此来告诉开发人员。@ConditionalOnMissingBean(HiddenHttpMethodFilter.class) 意思是容器中没有HiddenHttpMethodFilter这个bean时才把这个OrderedHiddenHttpMethodFilter组件纳入容器,如果有的话下面就不会再执行了
@ConditionalOnProperty源码如下
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用
String prefix() default "";//property名称的前缀,可有可无
String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用
String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的
}
@ConditionalOnProperty(prefix = “spring.mvc.hiddenmethod.filter”, name = “enabled”, matchIfMissing = false)这句话的意思是如果缺失了spring.mvc.hiddenmethod.filter.enabled这个属性,直接不自动装配,如果有这个属性,还得这个属性不为false才行(为空或值为其他什么值都没问题),详情参考:
http://www.voidcn.com/article/p-fcxykflh-brz.html
打开spring.mvc.hiddenmethod.filter.enabled一看就知道它默认是false,即不能使用这个过滤,要自己手动开启
这两个注解合起来的逻辑就是先判断容器里有没有HiddenHttpMethodFilter这个bean,如果我自己已经重写了一个,那下面就不能自动装配了,接下来检查这个spring.mvc.hiddenmethod.filter.enabled属性是否存在,不存在也就不用往下搞了(当然matchIfMissing这个判断默认就是false,加不加无所谓),然后如果这个属性存在还得不为false(其实就是个开关作用,默认false为关闭,也控制下面不能接下去搞,要开启得在配置文件中设置为true,意思是开启这个过滤器)。历尽重重关卡,最后终于可以把OrderedHiddenHttpMethodFilter这个bean纳入容器,让我们进去里面具体看下。
很显然它就是继承了HiddenHttpMethodFilter这个类,那么它做的事情默认没修改的话就是它的父类做的事情,那让我们再进它的父类HiddenHttpMethodFilter一窥究竟。
首先看看它的作用说明
大致的意思是这个类的作用是将post请求转换为其他http的方法,当前的浏览器仅仅支持get和post请求,要是想发送其他请求的方法有个通用的技巧,就是用一个隐藏字段,属性名叫“_method”,赋的值就是真实的发送请求。但是这个表单的发送请求必须得为post,如此才能被拦截器拦截,再通过包装处理变为真正的对应http请求。注意一点:这个被转换的http请求只能是“put”,“delete”,“patch”。还有这个“_method”默认参数名也可以在配置文件内改名为自己取的值。写法如下
下面来根据源代码看看
private static final List<String> 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";
@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);
}
核心的就是这个doFilterInternal方法,post请求才被拦截,是post请求且不为空,就取出这个“_method”属性对应的值,取出后要是不为空就统一转为大写,然后判断一下是不是PUT,DELETE,PATCH这三者之一,是的话就包装后放行。
提示:如果是客户端直接发起的请求,如postman模拟,或者安卓之类的发送put,delete可以直接发送,无需过滤包装放行。springboot之所以默认该过滤器是关闭的是因为现在都提倡前后端分类开发,前端页面不在这写,也就自然不会遇到这个问题,要是页面也放在这写,那么这个过滤器可以自己配下。
4.具体配置办法
上面写的比较零散,这里再来重复以下rest风格请求失败的解决办法
第一步:在application.properties里面开启这个过滤器,加入这句话
spring.mvc.hiddenmethod.filter.enabled=true
第二步:在表单提交时加入“_method”这个属性,对应value为真实请求方式,注意表单提交的方法得是post请求例如下面
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input value="rest put提交" type="submit" />
</form>
第三步:在控制器里面使用对应注解,例如put风格可以直接使用@PutMapping这个注解
@PutMapping("/user")
public String putUser(){
return "put-zs";
}
5.扩展
在ioc容器里面没有HiddenHttpMethodFilter这个bean是进行下面的自动配置的前提,那么我们自然可以不用它的,自己注册一个HiddenHttpMethodFilter组件放在ioc容器里面,灵活性更高。
下面实现一个个性化的小效果来说明这一思想
因为源码里面是提供了修改methodParam属性的接口的
测试可以发现此时,此时属性名已经被改为my_method才能生效了