RESTful风格
1、REST概念
REST(英文: Representational State Transfer,简称REST, 意思:表述性状态转换,描述了一个架构样式的网络系统,比如web应用)。
它是一种软件架构风格、 设计风格,而不是标准,只是提供了一组设计原则和约柬条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简介,更有层次,更易于实现缓存等机制。
它本身并没有什么使用性,其核心价值在于如何设计出符合REST风格的网络接口。
2、RESTful概念
REST指的是一组架构约束条件和原则。 满足这些约束条件和原则的应用程序或设计就是RESTful,
- RESTful的特性:
- 资源(Resources):互联网所有的事物都可以被抽象为资源。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI (統一资源定位符)指向它,每种资源对应一个特性的URI。 要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一 无二 的识别符。
- 表现层(Representation):把资源具体星现出来的形式,叫做它的表现层(Representation)。 比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。
- 状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换"(State Transfer)。而这种转换是建立在表现层之上的,所以就是”表现层状态转换"。
具体来说就是HTTP协议里面,四个表示操作方式的动词: GET、 POST、PUT、DELETE他们分别对应四种基本操作: GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
原来的操作资源的方式:
localhost:8080/getExpress.do?id=1
localhost:8080/saveExpress.do?
localhost:8080/updateExpress.do?
localhost:8080/deleteExpress.do?id=1
原来用的方式当然没有问题,但是如果有更简洁的方式就更好了,此时就是RESTful风格。
使用RESTful操作资源:
GET /expresses#查询所有的快递信息列表
GET /express/1006#查询一个快递信息
POST /express#新建一个快递信息
PUT/express/1006#更新一个快递信息(全部更新)
PATCH /express/1006#更新一个快递信息(部分更新)
DELETE /express/1006#删除一个快递信息
3、API设计/URL设计
动词+宾语
RESTful的核心思想就是:客户端的用户发出的数据操作指令都是”动词+宾语“的结构。比如,GET/expresses这个命令,GET是动词,/expresses是宾语。
于是,在编写controller的方法的时候,就可以使用springmvc提供的@RequestMapping注解的method属性来指定它可以 用于处理什么类型的请求。
动词通常就是五种HTTP方法,对应CRUD 操作。
GET:读取(Read)
POST:新建(Create)
PUT:更新(Update)
PATCH:更新(Update),通常是部分更新
DELETE:删除(Delete)
PS: 1、根据HTTP规范,动词一律大写。
2、一些代理只支持POST和GET方法,为了使用这些有限方法支持RESTful API, 需要一种办法覆盖http原来的方法。 使用订制的HTTP头X-HTTP-Method-Override来覆盖POST方法。
宾语必须是名词
宾语就是API的URL,是HTTP动词作用的对象。它应该是名词,不能是动词。
比如,/expresses 这个URL就是正确的。
以下这些URL都是不推荐的,因为带上了动词,不是推荐写法。
/getAllExpresses
/getExpress
/createExpress
/deleteAllExpress
避免多级URL
如果资源中有多级分类,也不建议写出多级的URL。例如要获取球队中某个队员,有人可能这么写:
GET /team/1001/player/1005
这种写法的语义不够明确,所以推荐使用查询字符串做后缀,改写为
GET /team/1001? player=1005.
再例如查询所有的还未职出的快递,你该如何编写呢?
GET /expresses/statu 不推荐
GET /expresses?statumfalse 推荐
4、HTTP状态码
客户端的用户发起的每一次请求, 服务器都必须给出响应。响应包括HTTP状态码和数据两部分。
HTTP状态码就是一个三位数,分成五个类别。这五大类总包含了100多种状态码(不需要全都记住,不用紧张哈,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。
五类状态码分别如下:
1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误
PS:API 不需要1xx状态码,所以这里可以忽略这个类别
状态码2xx
200状态码表示操作成功,但是不同的方法可以返回更精确的状态码
GRT:200 OK 表示一切正常
POST:201 Created 表示新的资源已经成功创建
PUT:200 OK
PATCH:200 OK
DELETE:204 No Content 表示资源已经成功删除
状态码3xx
API用不到301状态码(永久重定向)和302状态码(暂时重定向,307 也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API级别可以不考虑这两种情况。
API用到的3xx状态码,主要是303 See other,表示参考另一个URL。它与302和307的含义一样,也是暂时重定向",区别在于302和307用于GET请求,
而303用于POST、PUT 和DELETE请求。收到303以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。
我们只需要关注一下304状态码就可以了
304 : Not Modified 客户端使用缓存数据
状态码4xx
4xx状态码表示客户端错误。
400 Bad Request:服务器不理解客户端的请求,未做任何处理。
401 Unauthorized: 用户未提供身份验证凭据,或者没有通过身份验证。
403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。
404 Not Found: 所请求的资源不存在,或不可用。
405 Method Not Allowed: 用户已经通过身份验证,但是所用的HTtp 方法不在他的权限之内。
410 Gone:所请求的资源已从这个地址转移,不再可用。
415 Unsupported Media Type: 客户端要求的返回格式不支持。比如,API只能返回JSON 格式,但是客户端要求返回XL格式。
422 Unprocessable Entity : 客户端上传的附件无法处理,导致请求失败。
429 Too Many Requests:客户端的请求次数超过限额。
状态码5xx
5xx状态码表示服务端错误。一般来说,API不会向用户透露服务器的详细信息,所以只要两个状态码就够了。
500 Internal Server Error: 客户端请求有效,服务器处理时发生了意外。
503 Service Unavailable:服务器无法处理请求,一般用于网站护状态。
5、服务器响应
服务器返回的信息一般不推荐纯文本,而是建议大家选择JSON对象,因为这样才能返回标准的结构化数据。
所以,服务器回应的HTTP头的Content-Type属性要设为application/json.客户端请求时,也要明确告诉服务器,可以接受JSON格式,即请求的HTTP头的ACCEPT属性也要设成applicat ion/json.
当发生错误的时候,除了返回状态码之外,也要返回错误信息。所以我们可以自己封装要返回的信息。
6、案例
RESTful风格的查询
前端页面
<html>
<head>
<title>RESTful</title>
<script src="/js/jquery2.1.4.js"></script>
</head>
<body>
<form id="myForm" action="" method="post">
球队ID:<input type="text" name="teamID" id="teamID"><br/>
球队名称:<input type="text" name="tName" id="teamName"><br/>
球队位置:<input type="text" name="tLocation" id="teamLocation"><br/>
<button type="button" id="btnGetAll">查询所有GET</button>
<button type="button" id="btnGetOne">查询单个GET</button>
<button type="button" id="btnPost">添加POST</button>
<button type="button" id="btnPut">更新PUT</button>
<button type="button" id="btnDel">删除DELETE</button>
</form>
<p id="showResult"></p>
</body>
</html>
<script>
//页面加载完毕之后给按钮绑定事件
$(function (){
//1、给查询所有GET按钮绑定事件
$("#btnGetAll").click(function (){
$.ajax({
type: "GET",
url: "/restful/teams",
data: "",
success: function(list){
alert( "Data Saved: " + list );
var str = "";
for(var i=0;i<list.length;i++){
var obj=list[i];
str+="球队ID:"+obj.teamID+",球队名称:"+obj.tName+",球队位置:"+obj.tLocation+"<br/>";
}
$("#showResult").html(str);
}
});
});
//2、给查询单个GET绑定事件
$("#btnGetOne").click(function (){
$.ajax({
type: "GET",
url: "/restful/team/"+$("#teamID").val(),//RESTful风格的API定义(传参格式)
data: "",
success: function(obj){
if(obj == ""){
$("#showResult").html("此数据不存在");
return;
}
alert( "Data Saved: " + obj );
var str ="球队ID:"+obj.teamID+",球队名称:"+obj.tName+",球队位置:"+obj.tLocation+"<br/>";
$("#showResult").html(str);
}
});
});
//3、给添加POST绑定事件
$("#btnPost").click(function (){
alert($("#myForm").serialize());//输出测试
$.ajax({
type: "POST",
url: "/restful/team/",
data: $("#myForm").serialize(),//表单的所有数据以?&形式追加在URL后:team?teamID=1001&teamName=队伍&teamLocation=队伍地址
success: function(msg){
if(msg == "201"){
$("#showResult").html("添加成功");
return;
}
}
});
});
//4、给更新PUt绑定事件
$("#btnPut").click(function (){
$.ajax({
type: "POST",
url: "/restful/updateTeam/",
data: $("#myForm").serialize()+"&_method=PUT",//表单的所有数据以?&形式追加在URL后:team?teamID=1001&teamName=队伍&teamLocation=队伍地址
success: function(msg){
if(msg == "200"){
$("#showResult").html("更新成功");
return;
}else if(msg == ""){
$("#showResult").html("没找到对应的数据");
}
}
});
});
//5、给删除DELETE绑定事件
$("#btnDel").click(function (){
$.ajax({
type: "POST",
url: "/restful/deleteTeam/",
data: $("#myForm").serialize()+"&_method=DELETE",//表单的所有数据以?&形式追加在URL后:team?teamID=1001&teamName=队伍&teamLocation=队伍地址
success: function(msg){
if(msg == "204"){
$("#showResult").html("删除成功");
return;
}else if(msg == ""){
$("#showResult").html("没找到对应的数据");
}
}
});
});
});
</script>
/**
* RESTful风格控制器Controller
*/
@Controller
@RequestMapping("/restful")
public class RestfulController {
private static List<Team> teamList;//代替数据库存储数据
static { //静态代码块,类加载时存入数据用于查询测试
teamList = new ArrayList<>(3);
for(int i=1;i<=3;i++){
Team team = new Team();
team.setTeamID(1000+i);
team.settName("队名"+i);
team.settLocation("队伍地址"+i);
teamList.add(team);
}
}
/**
* 查询所有的球队
* @return
*/
@RequestMapping(value = "/teams",method = RequestMethod.GET)
@ResponseBody
public List<Team> getAll(){
System.out.println("查询所有数据------GETAll发起请求");
return teamList;
}
/**
* 查询单个球队
* @return
*/
@RequestMapping(value = "/team/{teamID}",method = RequestMethod.GET)
@ResponseBody
public Team getOne(@PathVariable("teamID") String teamID){
System.out.println("查询单个数据------GETOne发起请求");
for(Team team:teamList){
if(Integer.valueOf(teamID) == team.getTeamID()){
return team;
}
}
return null;
}
/**
* 添加球队信息
* @return
*/
@RequestMapping(value = "/team",method = RequestMethod.POST)
@ResponseBody
public String addPost(Team team){
System.out.println(team);
System.out.println("添加数据------addPost发起请求");
teamList.add(team);
return "201";
}
@RequestMapping(value = "/updateTeam",method = RequestMethod.PUT)
@ResponseBody
public String updatePut(Team team){
System.out.println("更新信息-----updatePUT发起请求");
for(Team t:teamList){
if(t.getTeamID()==team.getTeamID()){
t.settName(team.gettName());
t.settLocation(team.gettLocation());
return "200";
}
}
return null;
}
@RequestMapping(value = "/deleteTeam",method = RequestMethod.DELETE)
@ResponseBody
public String deleteDelete(Team team){
System.out.println(team);
System.out.println("删除信息-----DELETE发起请求");
for(Team t:teamList){
if(t.getTeamID()==team.getTeamID()){
teamList.remove(t);
return "204";
}
}
return null;
}
@RequestMapping("/hello")
public String hello(){
return "restful";
}
}
RESTful风格的更新和删除遇到的问题
原因
实际上,ajax中如果不用data传递参数,只使用url传递参数,是没有问题的。
在Ajax中,采用RESTful风格的PUT和DELETE请求传递参数无效,传递到后台的参数值为null的问题
原因:
Tomcat封装请求参数的过程:
1.将请求体中的数据,封装成一个map
2.request.getParameter("id")会从这个map中取值
3.SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();
AJAX发送PUT或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。
Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map, 只有POST形式的请求才封装请求为map.
解决方法
前端页面中发送ajax请求的时候在url中加"&_method=PUT"或者"&_method=DELETE"
如图:
然后web.xml中配置过滤器
配置的时候多个过滤器需要注意顺序
<!--使用Rest风格的URI将页面普通的post请求转为指定的delete或者put请求
原理:在Aajx中发送post请求后,带method参数,将其修改为PUT,或者DELETE请求-->
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>
org.springframework.web.filter.HiddenHttpMethodFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter </filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
自行封装响应结果
实际开发时,通常自行约定封装响应结果
如:自定义一个类,专门用于封装响应结果数据,controller中的返回类型永远是这个类的实体,前端要获取数据也只需要从这个实体内部取数据就可以了。
如下:
public class ResultVO<T> {
private int status;//状态码
private String msg;//响应信息
private List<T> list;//返回的数据可能是集合
private T obj;//返回的结果也有可能是对象。
public ResultVO(int status, String msg, T obj) {
this.status = status;
this.msg = msg;
this.obj = obj;
}
public ResultVO(int status, String msg, List<T> list) {
this.status = status;
this.msg = msg;
this.list = list;
}
public ResultVO(int status, String msg) {
this.status = status;
this.msg = msg;
}
public ResultVO() {//默认构造方法,根据实际情况自行决定
status = 200;
msg = "";
list = null;
obj = null;
}
}
如上,之前代码中的所有的返回值都可以封装为ResultVO类型,前端根据具体情况从返回的对象中获取数据即可。