RESTful风格

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协议里面,四个表示操作方式的动词: GETPOSTPUTDELETE他们分别对应四种基本操作: 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

ps:不要混淆名词单数和复数,为了保持简单,只对所有资源使用复数。

避免多级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 复制代码
<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>
java 复制代码
/**
 * 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中配置过滤器

配置的时候多个过滤器需要注意顺序

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中的返回类型永远是这个类的实体,前端要获取数据也只需要从这个实体内部取数据就可以了。

如下:

java 复制代码
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类型,前端根据具体情况从返回的对象中获取数据即可。

相关推荐
毅航几秒前
MyBatis 事务管理:一文掌握Mybatis事务管理核心逻辑
java·后端·mybatis
我的golang之路果然有问题15 分钟前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
柏油24 分钟前
MySql InnoDB 事务实现之 undo log 日志
数据库·后端·mysql
写bug写bug2 小时前
Java Streams 中的7个常见错误
java·后端
Luck小吕2 小时前
两天两夜!这个 GB28181 的坑让我差点卸载 VSCode
后端·网络协议
M1A13 小时前
全栈开发必备:Windows安装VS Code全流程
前端·后端·全栈
蜗牛快跑1233 小时前
github 源码阅读神器 deepwiki,自动生成源码架构图和知识库
前端·后端
嘻嘻嘻嘻嘻嘻ys3 小时前
《Vue 3.4响应式超级工厂:Script Setup工程化实战与性能跃迁》
前端·后端
橘猫云计算机设计3 小时前
net+MySQL中小民营企业安全生产管理系统(源码+lw+部署文档+讲解),源码可白嫖!
数据库·后端·爬虫·python·mysql·django·毕业设计
执念3653 小时前
MySQL基础
后端