文章目录
- RESTful
- 1. REST概念
- 2. RESTful概念
- 2.1 RESTful的特性
- 2.2 RESTful和原有方式操作资源的对比
- 3. API设计/URL设计
- 3.1 RESTful核心思想
- 3.2 RESTful中的五种动词
- 3.3 RESTful中的宾语
- 3.4 避免多级URL
- 4. HTTP状态码
- 4.1 状态码2xx
- 4.2 状态码3xx
- 4.3 状态码4xx
- 4.4 状态码5xx
- 5. 服务器响应
- 6. 案例
- 6.1 RESTful风格的查询
- 6.2 RESTful风格的添加
- 6.3 RESTful风格的更新
- 6.4 RESTful风格的删除
- 6.5 RESTful风格的更新和删除遇到的问题
- 6.5.1 原因
- 6.5.2 解决方法
- 7. 封装自定义响应数据(状态码+消息+数据)
RESTful
1. REST概念
表述性状态转换(Representational State Transfer,REST),描述了一个架构样式的网络系统,比如web应用)。它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简洁、更有层次、更易于实现缓存等机制。
它本身并没有什么使用性,其核心价值在于如何设计出符合REST风格的网络接口。
2. RESTful概念
REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。
2.1 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用来删除资源。
2.2 RESTful和原有方式操作资源的对比
- 原来操作资源的方式
- http://localhost:8080/getExpress.do?id=1
- http://localhost:8080/insertExpress.do
- http://localhost:8080/updatexpress.do?id=1
- http://localhost:8080/deleteExpress.do?id=1
- ……
- RESTful操作资源的方式
- GET /expresses #查询所有的快递信息列表
- GET /express/1006 #查询一个快递信息
- POST /express #新建一个快递信息
- PUT /express/1006 #更新一个快递信息(全部更新)
- PATCH /express/1006 #更新一个快递信息(部分更新)
- DELETE /express/1006 #删除一个快递信息
- ……
3. API设计/URL设计
3.1 RESTful核心思想
RESTful 的核心思想就是客户端的用户发出的数据操作指令都是"动词 + 宾语"的结构。
例如GET /expresses 这个命令,GET是动词,/expresses 是宾语。
3.2 RESTful中的五种动词
动词通常对应五种 HTTP 处理方法,对应 CRUD 操作,分别是:
- GET:读取(Read)
- POST:新建(Create)
- PUT:更新(Update)
- PATCH:更新(Update),通常是部分更新
- DELETE:删除(Delete)
注:
- 根据 HTTP 规范,动词一律大写。
- 一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖
http原来的方法。使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.
3.3 RESTful中的宾语
宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。
- 比如,/expresses 这个 URL 就是正确的。而对于 /getAllExpresses 、/getExpress 、/createExpress 等,这些URL都是不推荐的,因为带上了动词,不是推荐写法。
- 另外,不要混淆名词单数和复数,为了保持简单,只在操作所有资源的时候使用复数,其他(增删改)的情况一般都是使用单数。
3.4 避免多级URL
如果资源中有多级分类,也不建议写出多级的URL,尽量使用单级URL。
- 示例1:要获取球队中某个队员,有人可能这么写:GET /team/1001/player/1005 。但是,这种写法的语义不够明确,所以推荐使用查询字符串做后缀,改写为 GET /team/1001?player=1005 。
- 示例2:例如查询所有的还未取出的快递,使用 GET /expresses/statu 这种URL是不推荐的,使用 GET /expresses?statu=false 是推荐的。
4. HTTP状态码
客户端的用户发起的每一次请求,服务器都必须给出响应。响应包括 HTTP 状态码和数据两部分。
HTTP 状态码就是一个三位数,分成五个类别。这五大类总包含了100多种状态码解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。五类状态码分别为:1xx 相关信息、2xx 操作成功、3xx 重定向、4xx 客户端错误和 5xx 服务器错误。
注:API 不需要1xx状态码,所以这个类别直接忽略。
4.1 状态码2xx
200 状态码表示操作成功,但是不同的方法可以返回更精确的状态码。
GET: 200 OK | 表示一切正常 |
POST: 201 Created | 表示新的资源已经成功创建 |
PUT: 200 OK | 表示资源已经成功更新 |
PATCH: 200 OK | 表示部分资源已经成功更新 |
DELETE: 204 No Content | 表示资源已经成功删除 |
4.2 状态码3xx
API 用不到 301 状态码(永久重定向)和 302 状态码(暂时重定向, 307 也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。
API 用到的 3xx 状态码,主要是 303 See Other ,表示参考另一个 URL。它与 302 和 307 的含义一样,也是"暂时重定向",区别在于 302 和 307 用于 GET 请求,而 303 用于 POST 、 PUT 和 DELETE 请求。收到 303 以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。
304 Not Modified | 客户端使用缓存数据 |
4.3 状态码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 格式,但是客户端要求返回 XML 格式。 |
422 Unprocessable Entity | 客户端上传的附件无法处理,导致请求失败。 |
429 Too Many Requests | 客户端的请求次数超过限额。 |
4.4 状态码5xx
5xx 状态码表示服务端错误。一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。
500 Internal Server Error | 客户端请求有效,服务器处理时发生了意外。 |
503 Service Unavailable | 服务器无法处理请求,一般用于网站维护状态。 |
5. 服务器响应
服务器返回的信息一般不推荐纯文本,而是建议大家选择JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的 Content-Type 属性要设为 application/json 。客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的 ACCEPT 属性也要设成 application/json 。
当发生错误的时候,除了返回状态码之外,也要返回错误信息。所以我们可以自己封装要返回的信息。
6. 案例
先引入json的依赖、实体类的创建(这里就不赘述了),然后前端页面准备:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>restful</title>
<script src="/js/jquery-1.11.1.js"></script>
</head>
<body>
<form id="myForm" action="" method="post">
球队ID:<input type="text" name="teamId" id="teamId" /><br/>
球队名称:<input type="text" name="teamName" /><br/>
球队位置:<input type="text" name="location" /><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 () {
//给 查询所有GET 按钮绑定单击事件
//给 查询单个GET 按钮绑定单击事件
//给 查询添加POST 按钮绑定单击事件
//给 查询删除DELETE 按钮绑定单击事件
});
</script>
6.1 RESTful风格的查询
- 前端AJAX请求
//给 查询所有GET 按钮绑定单击事件
$("#btnGetAll").click(function () {//发起异步请求
$.ajax({
type: "GET",
url: "teams", //RESTful风格的API定义
data: "",
dataType:"json",
success: function(list){
alert( "Data Saved: " + list );
var str="";
for(var i=0;i<list.length;i++){
var obj=list[i];
str+=obj.teamId+"----"+obj.teamName+"----"+obj.location+"<br/>";
} $
("#showResult").html(str);
}
});
});
//给 查询单个GET 按钮绑定单击事件
$("#btnGetOne").click(function () {//发起异步请求
$.ajax({
type: "GET",
url: "team/"+$("#teamId").val(), //RESTful风格的API定义
data: "",
dataType:"json",
success: function(obj){
alert( "Data Saved: " + obj );
if(obj==""){
$("#showResult").html("没有符合条件的数据!");
}else
$("#showResult").html(obj.teamId+"----"+obj.teamName+"----"+obj.location+"<br/>");
}
});
});
- 后端控制器处理部分
@RequestMapping(value = "teams", method = RequestMethod.GET)
@ResponseBody
public List<Team> getTeamList() {
System.out.println("查询所有GET-----------");
System.out.println(teamList);
return teamList;
}
@RequestMapping(value = "team/{id}", method = RequestMethod.GET)
@ResponseBody
public Team getTeam(@PathVariable("id") int id) {
System.out.println("查询单个GET-----------");
for (Team team : teamList) {
if (team.getTeamId() == id){
return team;
}
}
return null;
}
6.2 RESTful风格的添加
- 前端AJAX请求
//给 查询添加POST 按钮绑定单击事件
$("#btnPost").click(function () {//发起异步请求
alert($("#myForm").serialize());
$.ajax({
type: "POST",
url: "team", //RESTful风格的API定义
data: $("#myForm").serialize(),
//表单的所有数据以?&形式追加在URL后面 例如 /restful/team?teamId=1006&teamName=kuaichuan&location=las
dataType:"json",
success: function(msg){
//alert( "Data Saved: " + msg );
$("#showResult").html(msg ? "添加成功":"添加失败");
}
});
});
- 后端控制器处理部分
@RequestMapping(value = "team", method = RequestMethod.POST)
@ResponseBody
public boolean insertTeam(Team team) {
System.out.println("添加POST-----------");
boolean add = teamList.add(team);
System.out.println(teamList);
return add;
}
6.3 RESTful风格的更新
- 前端AJAX请求
//给 查询更新PUT 按钮绑定单击事件
$("#btnPut").click(function(){
alert($("#myForm").serialize());
$.ajax({
type: "POST",
//type: "PUT",//这种也可以,但是就不能在data传递数据了,原因看6.5小节
url: "team/"+$("#teamId").val(),
//data: $("#myForm").serialize(),
data: $("#myForm").serialize()+"&_method=PUT",
dataType:"json",
//headers:{"X-HTTP-Method-Override":"GET"},
success: function(msg){
$("#showResult").html(msg ? "更新成功" : "更新失败");
}
});
});
- 后端控制器处理部分
@RequestMapping(value = "team/{id}", method = RequestMethod.PUT)
@ResponseBody
public boolean updateTeam(@PathVariable("id") int id, Team team) {
boolean flag = false;
System.out.println("更新PUT-----------");
for (Team team1 : teamList) {
if (team1.getTeamId() == id){
team1.setLocation(team.getLocation());
flag = true;
break;
}
}
System.out.println(teamList);
return flag;
}
6.4 RESTful风格的删除
- 前端AJAX请求
//给 查询删除DELETE 按钮绑定单击事件
$("#btnDel").click(function(){
alert($("#myForm").serialize());
$.ajax({
type: "DELETE",
url: "team/"+$("#teamId").val(),
//data: $("#myForm").serialize(),
//data: $("#myForm").serialize()+"&_method=DELETE",
dataType:"json",
//headers:{"X-HTTP-Method-Override":"GET"},
success: function(msg){
$("#showResult").html(msg ? "删除成功" : "删除失败");
}
});
});
- 后端控制器处理部分
@RequestMapping(value = "team/{id}", method = RequestMethod.DELETE)
@ResponseBody
public boolean deleteTeam(@PathVariable("id") int id) {
boolean flag = false;
System.out.println("删除DELETE-----------");
for (Team team1 : teamList) {
if (team1.getTeamId() == id){
teamList.remove(team1);
flag = true;
break;
}
}
System.out.println(teamList);
return flag;
}
6.5 RESTful风格的更新和删除遇到的问题
在Ajax中,采用Restful风格PUT和DELETE请求传递参数(data)无效,传递到后台的参数值为null。
6.5.1 原因
Tomcat封装请求参数的过程:
- 将请求体中的数据,封装成一个map;
- request.getParameter(key)会从这个map中取值
- SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();
AJAX发送PU或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。因为Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。
6.5.2 解决方法
- 前端页面中改用ajax发送POST请求,并在url中加 &_method=”PUT” 或者 &_method=”DELETE” 即可;
- web.xml中进行配置HiddenHttpMethodFilter 。
注意以下过滤器的配置顺序。
<!-- 使用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>
7. 封装自定义响应数据(状态码+消息+数据)
可以对要响应回去的状态码、消息、数据进行一个封装,然后返回即可。同样的道理,还可以把响应码、响应的消息做成枚举类,让后端去处理的时候做到见名知意,这里直接简单地给出一个简易封装的实体类,不多赘述。
public class AjaxResultVO<T> {
private Integer code;//响应的状态码
private String msg;//响应的消息
private List<T> dataList;//响应的数据集合
private T data;//响应的数据
public AjaxResultVO() {
code = 200;
msg = "";
dataList = null;
data = null;
}
public AjaxResultVO(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public AjaxResultVO(Integer code, String msg, List<T> dataList) {
this.code = code;
this.msg = msg;
this.dataList = dataList;
}
public AjaxResultVO(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public AjaxResultVO(Integer code, String msg, List<T> dataList, T data) {
this.code = code;
this.msg = msg;
this.dataList = dataList;
this.data = data;
}
//getter && setter
}