Restful的使用

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)

注:

  1. 根据 HTTP 规范,动词一律大写。
  2. 一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖
    http原来的方法。使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.

3.3 RESTful中的宾语

​ 宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。

  1. 比如,/expresses 这个 URL 就是正确的。而对于 /getAllExpresses 、/getExpress 、/createExpress 等,这些URL都是不推荐的,因为带上了动词,不是推荐写法。
  2. 另外,不要混淆名词单数和复数,为了保持简单,只在操作所有资源的时候使用复数,其他(增删改)的情况一般都是使用单数。

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风格的查询

  1. 前端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/>");
            }
        });
    });
    
  2. 后端控制器处理部分

    @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风格的添加

  1. 前端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 ? "添加成功":"添加失败");
            }
        });
    });
    
  2. 后端控制器处理部分

    @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风格的更新

  1. 前端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 ? "更新成功" : "更新失败");
            }
        });
    });
    
  2. 后端控制器处理部分

    @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风格的删除

  1. 前端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 ? "删除成功" : "删除失败");
            }
        });
    });
    
  2. 后端控制器处理部分

    @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封装请求参数的过程:

  1. 将请求体中的数据,封装成一个map;
  2. request.getParameter(key)会从这个map中取值
  3. SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();

AJAX发送PU或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。因为Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。

6.5.2 解决方法
  1. 前端页面中改用ajax发送POST请求,并在url中加 &_method=”PUT” 或者 &_method=”DELETE” 即可;

在这里插入图片描述

  1. 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
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值