restful api规范

Rest接口约束定义为: 资源识别;请求动作;响应信息

**

一、资源识别

**
URL:

  URL表示资源,资源一般对应服务器端领域模型中的实体类。
  URL的表示:域名+api;https://api.example.com/v1/

URL命名规范:

   1.小写
   2.用-不用_
   3.参数列表要编码
   4.用名词表示资源名称
   5.用单数表示一个资源,用复数表示资源集合。
   6.在url中不允许出现文件扩展名. 比如接口为 /xxx/api, 不要写成 /xxx/api.php 这样的是不合法的。

**

二、请求动作

**
http1.1方法:

OPTIONS(获取服务器信息)
HEAD(请求资源首部信息)
GET(获取资源)
POST(创建资源)
PUT(更新资源全部信息)
PATCH(更新资源部分信息)
DELETE(删除资源)

幂等性与安全性:

安全性:保证资源状态不变,或者说资源的表现形式不变。
幂等性:对于多次执行结果是一样的,如a=4(幂等);a++(非幂等)

为什么需要考虑幂等:

    当你向服务发送一个 POST 请求过程中超时将会发生什么,是不是资源已经被更新?
    超时发生在向服务器发送请求阶段还是服务返回客户端阶段?我们是否能够安全地重试一遍,或查出资源究竟发生了什么变化?

在这里插入图片描述
关于patch是非幂等的解释:PUT是根据客户端提供了完整的资源数据,客户端提交什么就替换什么,而PATCH有可能是根据客户端提供的参数或者指令,动态的计算出某个值,例如每次请求后资源的某个参数减1,所以多次调用,资源会有不同的变化。所以PATCH是非幂等的。op表示对资源的操作,总共有六种:add,replace,remove,move,copy,test

  1. add 添加:{“op”: “add”, “path”: “/xxx”, “value”: “xxx”},如果该属性不存,那么就添加该属性,如果属性存在,就改变属性的值。
  2. remove 删除:{“op”: “remove”, “path”: “/xxx”},删除某个属性,或把它设为默认值(例如空值)。
  3. replace 替换:{“op”: “replace”, “path”: “/xxx”, “value”: “xxx”},改变属性的值,也可以理解为先执行了删除,然后进行添加。
  4. copy 复制:{“op”: “copy”, “from”: “/xxx”, “path”: “/yyy”},把某个属性的值赋给目标属性。
  5. remove 移动:{“op”: “move”, “from”: “/xxx”, “path”: “/yyy”},把源属性的值赋值给目标属性,并把源属性删除或设成默认值。
  6. test测试:{“op”: “test”, “path”: “/xxx”, “value”: “xxx”},测试目标属性的值和指定的值是一样的。

执行每个操作的代码如下,其中add和remove操作可以验证patch请求的非幂等性:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatch;

public class JsonPatchDemo {

    public static void main(String[] args) {
        String origin = "{\"userName\":\"AA\", \"userAccount\":\"12345\", \"userPwd\":\"8888\",\"character\":[\"qiu\",\"dan\"]}";
        String patch = "["
                + "{\"op\":\"add\", \"path\":\"/character/0\", \"value\":\"shao\"}"
                + "]";
        String patch0 = "[]";
        String patchreplace="["
                + "{\"op\":\"replace\", \"path\":\"/userName\", \"value\":\"BB\"}"
                + "]";
        String patchremove="["
                + "{\"op\":\"remove\", \"path\":\"/userAccount\"}"
                + "]";
        String patchmove="["
                + "{\"op\":\"move\", \"from\":\"/userName\",\"path\":\"/userAccount\"}"
                + "]";
        String patchremovearry="["
                + "{\"op\":\"remove\", \"path\":\"/character/0\"}"
                + "]";
        String patchcopy="["
                + "{\"op\":\"copy\", \"from\":\"/userName\",\"path\":\"/userAccount\"}"
                + "]";

        String patchtest="["
                + "{\"op\":\"test\",\"path\":\"/userAccount\",\"value\":\"12345\"},"
                +"{\"op\": \"replace\", \"path\": \"/userAccount\", \"value\": \"78910\"}"
                + "]";
        String patchtest1="["
                + "{\"op\":\"test\",\"path\":\"/userAccount\",\"value\":\"11111\"},"
                +"{\"op\": \"replace\", \"path\": \"/userAccount\", \"value\": \"78910\"}"
                + "]";

        String jsonReplace=patchApplication(origin, patchreplace);
        String jsonRemove=patchApplication(origin, patchremove);
        String jsonMove=patchApplication(origin, patchmove);

        String afterJson0 = patchApplication(origin, patch0);
        String afterJson = patchApplication(origin, patch);
        String afterJson1 = patchApplication(afterJson, patch);

        String jsonremovearry1=patchApplication(afterJson1, patchremovearry);

        String jsonremovearry2=patchApplication(jsonremovearry1, patchremovearry);

        String jsonCopy = patchApplication(origin, patchcopy);

        String jsonTest = patchApplication(origin, patchtest);

        String jsonTest1 = patchApplication(origin, patchtest1);
        System.out.println("****************************************************************************************************************************");
        System.out.println("原来的json对象:"+afterJson0);
        System.out.println("执行一次add命令后的json对象:"+afterJson);
        System.out.println("执行两次add命令后的json对象:"+afterJson1);
        System.out.println("****************************************************************************************************************************");
        System.out.println("原来的json对象:"+afterJson1);
        System.out.println("执行一次remove命令后的json对象:"+jsonremovearry1);
        System.out.println("执行两次remove命令后的json对象:"+jsonremovearry2);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行replace替换用户名后的json对象:"+jsonReplace);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行remove删除用户账号后的json对象:"+jsonRemove);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行move将name的值移动到account后删除name的json对象:"+jsonMove);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行copy将name的值移动到account的json对象:"+jsonCopy);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行test判断成功并替换的json对象:"+jsonTest);
        System.out.println("****************************************************************************************************************************");
        System.out.println("执行test未判断成功并替换的json对象:"+jsonTest1);
    }

    /**
     * @param origin        原JSON
     * @param patch         patch信息
     * @return
     */
    public static String patchApplication(String origin, String patch) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            // 将原json转为JsonNode
            JsonNode originJson = objectMapper.readTree(origin);
            // 将patch json转为 JsonNode
            JsonNode patchJson = objectMapper.readTree(patch);
            // 将patch形式的json转为 JsonPatch
            JsonPatch patcher = JsonPatch.fromJson(patchJson);

            // patch应用替换原JSON
            JsonNode afterJson = patcher.apply(originJson);
            return afterJson.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


}

执行结果截图:
在这里插入图片描述

put与post区别:

   1.博客系统提供一个Web API,模式是这样http://superblogging/blogs/{blog-name}
   将{blog-name}替换为我们的blog名字,往这个URI发送一个HTTP PUT或者POST请求,HTTP的body部分就是博文.
   post:创建两个博文.put:后一个博文覆盖前一个博文。
   2.当你知道一个文章存在 http://example.org/article/1234, 你可以使用 [PUT]来直接更新这篇文章的内容。
   当你不能确实一个资源的具体位置时,如当你需要创建一篇新文章,但不知道它会存在什么地方,
   你可以 [POST] 这篇文章到一个URL,并让服务器决定它的真正URL。
   在put后加入HTTP/1.1 201 Created. Location: /article/1234 后可以让put实现post的功能。

get与post区别:

   1.get请求:参数在url后,post请求:参数在请求体中.
   2.get只需要解析参数,约定好不会传递数据不会有body。post请求,先按照get请求的处理流程。把参数解析完再去处理body。
   @RequestBody接受对象只能只能接受POST或着PUT请求的对象;
   @RequestParam接受普通的请求参数。GET请求在url上的复杂对象并不能接收。

put与Patch的区别:

    假设我们有一个UserInfo,里面有userId, userName, userGender等10个字段。只需要修改userName。
    如果是post:将修改了username的完整的UserInfo对象,如果不是完整的UserInfo对象,缺失的字段将会被清空。
    如果是patch:只需要传递username参数。

过滤信息:

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

还有一个较为严重的问题是传统的Web MVC框架基本上都只支持GET和POST两种HTTP方法,而不支持PUT和DELETE方法。
**

三、响应信息

**
状态码:

1xx: 信息,请求收到了,继续处理。
2xx: 代表成功. 行为被成功地接收、理解及采纳。
3xx: 重定向。
4xx: 客户端错误,请求包含语法错误或请求无法实现。
5xx: 服务器端错误.

统一返回数据格式:

RESTful规范中的请求应该返回统一的数据格式。对于返回的数据,一般会包含如下字段:

1) code: http响应的状态码。
2) status: 包含文本, 比如:'success'(成功), 'fail'(失败),  'error'(异常) 
         HTTP状态响应码在500-599之间为 'fail'; 
         在400-499之间为 'error', 其他一般都为 'success'。 
         对于响应状态码为 1xx, 2xx, 3xx 这样的可以根据实际情况可要可不要。
         当status的值为 'fail' 或 'error'时,需要添加 message 字段,用于显示错误信息。
3) data: 当请求成功的时候, 返回的数据信息。 但是当状态值为 'fail' 或 'error' 时,data仅仅包含错误原因或异常信息等。

返回成功信息:

{
    "code": 200,
    "status": "success",
    "data": [{
        "userName": "tugenhua",
        "age": 31
    }]
}

返回失败信息:

{
    "code": 401,
    "status": "error",
    "message": '用户没有权限',
    "data": null
}