理解 RESTful

作者: 独木舟的木 | 来源:发表于2019-07-05 16:12 被阅读11次

    REST 的定义

    REST 一词是由 Roy Fielding 博士于 2000 年在他的博士论文 Architectural Styles and the Design of Network-based Software Architecture 中提出的,实际指一种有助于创建和组织分布式系统的架构风格。它并不是一个标准或准则,而是一种基于资源的架构风格

    基于 REST 风格构建的 API 应该满足 CS 模式交互、统一的资源接口、透明的分层系统以及支持无状态和缓存等条件约束,另外需要保证 API 的安全性等。

    以 REST 风格构建的系统将在性能、组件交互的可扩展性、接口的简单性、可移植性、可靠性、可见性等多个方面得到提升和优化。

    ——摘自《RESTful API 开发实战》

    注:Roy Fielding 是 HTTP 协议(1.0 版和 1.1 版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席。

    REST = Representational State Transfer,表述性状态传递/表现层状态转移。

    REST 描述网络中服务器和客户端的交互形式。

    资源在网络中以某种表现形式进行状态转移。分解开来:

    Resource:资源,即数据。比如 newsfeed,friends 等;
    Representational:某种表现形式,比如用 JSON,XML,JPEG 等;
    State Transfer:状态变化。通过 HTTP 动词实现。

    1. 每一个 URI 代表一种资源;
    2. 客户端和服务器之间,传递这种资源的某种表现层(资源的呈现形式);
    3. 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现 "表现层状态转化"。

    总结:用 URL 定位资源,用 HTTP 动词(GET、POST、PUT、DELETE)描述操作,实现资源的状态转换。

    REST 的基本原理

    REST 基于资源架构,资源可通过基于 HTTP 标准方法的通用接口访问。REST 要求开发人员显式使用 HTTP 方法,并以符合协议定义的方式使用。每个资源都有一个 URL 标识,且都应该支持 HTTP 的通用操作,同时 REST 允许该资源有不同的表现形式,例如文本、XML、JSON 等。REST 客户端可以通过 HTTP 协议(内容协商,服务端以正确的格式响应内容)请求特定的表现形式。

    数据元素 描述
    资源 超文本引用的概念目标,例如 customer/order
    资源标识符 统一资源定位符(URL)或统一资源名称(URN)标识特定的资源
    资源元数据 描述资源信息,如标签、作者、源链接、替代位置、别名
    表现形式 资源内容——JSON 消息、HTML 文档、JPEG 图片
    表现元数据 描述如何处理表现的信息,如媒介类型、最后修改时间
    控制数据 描述如何优化响应处理的信息,例如 if-modified-since、cache-control-expiry

    操作类型

    通过 HTTP 动词来操作资源。

    HTTP 方法 描述
    GET 从服务器上获取资源(一项或多项)
    POST 在服务器新建一个资源
    PUT 在服务器更新资源(更新资源整体)
    PATCH 在服务器更新部分资源(更新资源的某一项属性)
    DELETE 从服务器删除资源
    HEAD(不常用) 获取资源的元数据(数据的哈希值、最后更新时间...)
    OPTIONS(不常用) 获取信息,关于资源的哪些属性是客户端可以操作的

    安全性和幂等性

    安全性:不会改变资源状态,可以理解为只读的;

    幂等性:无边际效应,多次操作得到相同的结果。

    当向系统发送 GETDELETEPUT 方法时,不论该方法发送多少次,执行的效果应该是一样的。但是 POST 方法在集合中创建了实体,因此不是幂等的。

    HTTP 动词 安全性 幂等性
    GET
    POST
    PUT
    DELETE

    安全性和幂等性均不保证反复请求能拿到相同的 response。以 DELETE 为例,第一次 DELETE 返回 200 表示删除成功,第二次返回 404 提示资源不存在,这是允许的。

    预期的返回结果

    当使用不同的 HTTP 动词向服务器发送请求时,服务器向客户端返回的结果应该符合以下规范:

    HTTP 方法 路径 response 格式
    GET /collection 返回资源对象的列表(数组)
    GET /collection/resource 返回单个资源对象
    POST /collection 返回新生成的资源对象
    PUT /collection/resource 返回完整的资源对象
    PATCH /collection/resource 返回完整的资源对象
    DELETE /collection/resource 返回一个空文档

    API 设计策略

    1. 螺栓策略;
    2. 绿地策略;
    3. 敏捷策略;
    4. 外观策略;

    RESTful API 设计的最佳实践

    1. 保持基础 URL 简明直观

    简明直观的 URL 设计可以体现 API 的功能可见性(接口交互双方不需要文档就可以理解使用这一设计特性)。

    对于 Web API 而言,每个资源都应该只有两个基础 URL:

    序号 基础URL 描述
    1 /customers 集合
    2 /customers/{id} 集合中的某个特定元素

    使用 HTTP 动词对资源集合和元素进行操作:

    资源 POST 创建 GET 读取 PUT 更新 DELETE 删除
    /customers 新建客户 客户列表 批量更新 删除所有
    /customers/{id} -- 展示某个客户 客户存在就更新,不存在就报错 删除某个客户

    2. 错误处理

    优秀的异常和错误处理设计对 API 设计者来说非常重要。

    一个 API 应当以一种已知的可使用的格式来提供有用的错误信息。

    API 应当总是返回有意义的 HTTP 状态代码。API 错误通常被分成两种类型:代表客户端问题的 400 系列状态码和代表服务器问题的 500 系列状态码。最简情况下,API 应当把便于使用的 JSON 格式作为 400 系列错误的标准化表示。如果可能 (意思是,如果负载均衡和反向代理能创建自定义的错误实体), 这也适用于 500 系列错误代码。

    {
      "code" : 1234,
      "message" : "Something bad happened",
      "description" : "More details about the error here"
    }
    

    对 PUT, PATCH 和 POST 请求进行错误验证将需要一个字段分解。下面可能是最好的模式:使用一个固定的顶层错误代码来验证错误,并在额外的字段中提供详细错误信息,就像这样:

    {
      "code" : 1024,
      "message" : "Validation Failed",
      "errors" : [
        {
          "code" : 5432,
          "field" : "first_name",
          "message" : "First name cannot have fancy characters"
        },
        {
           "code" : 5622,
           "field" : "password",
           "message" : "Password cannot be blank"
        }
      ]
    }
    

    3. HTTP 状态码

    一个请求是否成功是由 HTTP 状态码标明的。一个 2XX 的状态码表示成功,而一个 4XX 表示请求失败。

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。表示资源在终端不再可用。
    415 Unsupported Media Type (不支持的媒体类型) - 如果请求中包含了不正确的内容类型
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    429 Too Many Requests (请求过多) - 当请求由于访问速率限制而被拒绝时
    
    /**
    * 在接口处理发生错误的时候,如果是客户端请求参数导致的错误,返回 4xx 状态码,
    * 如果是服务端自身的处理逻辑错误,返回 5xx 状态码。
    * 所有的异常对象都是对这个异常状态的描述,其中 error 字段是错误的描述,detail 字段(可选)是导致错误的详细原因。
    */
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功
    533 INTERNAL SERVER ERROR - [*]:keystore 不存在
    534 INTERNAL SERVER ERROR - [*]:keystore 已过期
    

    HTTP 状态码参考:

    4. 版本控制

    一个好的 RESTful API 会在 URL 中包含版本信息。另一种比较常见的方案是在 HTTP 请求头(Accept 字段)中包含版本信息。但是与很多不同的第三方开发者一起工作之后,我可以很明确的告诉你,在 HTTP 请求头中包含版本信息远没有将其放在 URL 中来的容易(方便),而且将版本号放在 URL 中也更加直观明了。

    • API 版本必须是强制性的。
    • 向后兼容性:必须在引入新版本 API 的同时保持旧版本 API 仍然可用。

    常见的根 URL 示例:

    // 将 API 放在主域名下:
    https://www.example.com/api/v1/*
    https://www.example.com/api/v2/*
    
    // 将 API 部署在专用域名之下:
    https://api.example.com/v1/*
    https://api.example.com/v2/*
    

    5. 过滤、排序和搜索

    API 应该向开发人员提供他们需要的信息,而不是所有信息。

    复杂查询可以带上过滤条件,常见参数:

    常见过滤类型 参数示例 描述
    偏移 ?offset=10 指定返回记录的开始位置
    限制 ?limit=10 指定返回记录的数量
    页数 ?page=2&per_page=100 指定第几页,以及每页的记录数
    排序 ?sortby=name&order=asc 指定返回结果按照哪个属性排序,以及排序顺序。
    过滤 ?animal_type_id=1 指定筛选条件
    ?type=1&age=16 指定筛选条件
    投影 ?whitelist=id,name,email
    局部响应 ?fields=title,media 仅返回部分信息,标题和内容

    6. 多格式支持(只返回 JSON)

    数据交换格式:建议使用基于 JSON 的媒体类型(Content-Type: application/json

    POST /v1/animal HTTP/1.1
    Host: api.example.org
    Accept: application/json
    Content-Type: application/json
    Content-Length: 24
    
    {   
      "name": "Gir",
      "animalType": "12"
    }
    

    不推荐使用 XML 作为数据交换格式。

    Google 趋势图,比较 XML 和 JSON API

    7. 端点(EndPoints)

    一个端点就是指向特定资源或资源集合的 URL。

    API 端点的名词应该使用复数而不是单数。

    如果你正在构建一个虚构的 API 来展现几个不同的动物园,每个动物园又包含动物、员工和每个动物的物种,你可能会有如下的端点信息:

    针对每个端点,你可能想列出所有可行的 HTTP 动词和端点的组合:

    HTTP 方法 路径 描述
    GET /zoos List all Zoos (ID and Name, not too much detail)
    GET /zoos/ZID Retrieve an entire Zoo object
    POST /zoos Create a new Zoo
    PUT /zoos/ZID Update a Zoo (entire object)
    PATCH /zoos/ZID Update a Zoo (partial object)
    DELETE /zoos/ZID Delete a Zoo
    GET /zoos/ZID/animals Retrieve a listing of Animals (ID and Name).
    GET /animals List all Animals (ID and Name).
    POST /animals Create a new Animal
    GET /animals/AID Retrieve an Animal object
    PUT /animals/AID Update an Animal (entire object)
    PATCH /animals/AID Update an Animal (partial object)
    GET /animal_types Retrieve a listing (ID and Name) of all Animal Types
    GET /animal_types/ATID Retrieve an entire Animal Type object
    GET /employees Retrieve an entire list of Employees
    GET /employees/EID Retreive a specific Employee
    GET /zoos/ZID/employees Retrieve a listing of Employees (ID and Name) who work at this Zoo
    POST /employees Create a new Employee
    POST /zoos/ZID/employees Hire an Employee at a specific Zoo
    DELETE /zoos/ZID/employees/EID Fire an Employee from a specific Zoo

    Egg.js 示例

    使用 Node.js 的 Egg.js 框架编写 RESTful API 示例。

    Method Path Router Name Controller.Action Description
    GET /posts posts app.controllers.posts.index 返回所有资源
    GET /posts/new new_post app.controllers.posts.new ???
    GET /posts/:id post app.controllers.posts.show 返回一个资源
    GET /posts/:id/edit edit_post app.controllers.posts.edit ?修改某一条条目
    POST /posts posts app.controllers.posts.create 新建一个资源
    PUT /posts/:id post app.controllers.posts.update 更新资源
    DELETE /posts/:id post app.controllers.posts.destroy 删除资源

    app/router.js

    module.exports = app => {
      const { router, controller } = app;
      // app.resources('routerName', 'pathMatch', controller);
      router.resources('posts', '/api/posts', controller.posts);
    };
    

    app/controller/posts.js

    // app/controller/posts.js
    
    // GET - 查询所有记录
    exports.index = async () => {};
    
    // GET - 查询某一条资源明细
    exports.new = async () => {};
    
    // POST - 增
    exports.create = async () => {};
    
    // GET - 查询某一条记录
    exports.show = async () => {};
    
    // ?
    exports.edit = async () => {};
    
    // PUT - 更新
    exports.update = async () => {};
    
    // DELETE - 删除
    exports.destroy = async () => {};
    

    名词释义

    常见的数据格式:

    • XML(Extensible Markup Language,可扩展标记语言);
    • JSON(JavaScript Object Notation,JavaScript 对象表示法);

    常见的传输协议或 Web 服务:

    • SOAP(Simple Object Access Protocol,简单对象访问协议);
    • REST(Representational State Transfer,表述性状态传递);

    其他:

    • API(Application Programming Interface,应用程序编程接口);
    • APX(Application Programming Experience,应用编程体验);
    • HATEOAS(Hypermedia as the Engine of Application State, 超媒体作为应用程序状态引擎)

    参考

    相关文章

      网友评论

        本文标题:理解 RESTful

        本文链接:https://www.haomeiwen.com/subject/lsuehctx.html