只会一点点 RESTful API

avatar
FE @字节跳动

REST 介绍

解释

英文:Representational State Transfer

白话:表现层状态转移?

背景

首次出现在2000年 Roy Thomas Fielding 罗伊·托马斯·菲尔丁(HTTP规范的主要编写者之一)的博士论文中。 他的论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST 指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为 RESTful 架构。

论文地址:

Architectural Styles and the Design of Network-based Software Architectures

REST章节:

CHAPTER 5 Representational State Transfer (REST)

理解

实际上 REST 其实它真正的全称是 Resource Representational State Transfer,我们拆开来理解。

  • 资源- Resources

    •   宇宙万物我们都可以理解成"资源",包括我们自然界存在的物体或者在网络上虚拟的物品,都可以理解成一个资源,比如一个人,一棵树,一张 vip 卡,一枚游戏币。我们用 URI(统一资源定位符)指向对应资源,每种资源都有个特定的 URI。那么我们就可以通过URI来定位某个资源,因此 URI 就成了每一个资源的地址或独一无二的识别符。
  • 表现层- Representational

    •   "资源"是存在我们脑海中的,对现实世界和虚拟世界中物资的一个认知概念,在计算机中,我们需要把"资源"通过计算机的描述语言表现出来,我们可以叫做它的"表现层"(Representation)。。
    •   比如,我们可以用 json 来描述表示一种资源,也可以用 XML,HTML 等格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。
  • 状态转化-State Transfer

    •   我们的网站和各种站点,比如博客,我们可以添加一篇文章,也可以修改,删除一篇文章。那么我们就是在对文章这个资源进行操作,操作的过程中势必会涉及到状态的变更转化。然而 HTTP 协议是无状态的,也就意味着服务端要承担状态存储,我们需要通过某些手段让服务端资源状态转化,而状态转化是可以通过表现层体现,所以就是"表现层状态转化"。

注意:REST 中的资源和数据并不等同,资源是数据和表现层的结合。比如“世界上最爱我的3个人”,“世界上最有钱的3个人”,“世界上最漂亮的3个人”在数据上看是有可能重叠或者相同,由于他们表现层不同,所以被归为不同的资源,这也是 REST 的全名是 Representational State Transfer 的原因。

基本原则

标准的 REST 约束应满足以下6个原则:

  1. 客户端-服务端(Client-Server): 前后端分离,把客户端和服务端的关注点分离,提高跨平台的可移植性,使得服务端独立可更好服务于前端、安卓等各类型客户端设备。

  2. 无状态(Stateless):服务端不保存客户端状态,客户端每次请求都需要携带状态信息。

    • 应用(请求)状态:指的是与某一特定请求相关的状态信息。
    • 资源状态:则反映了某一存储在服务器端资源在某一时刻的特定状态,该状态不会因为用户请求而改变,任何用户在同一时刻对该资源的请求都会获得这一状态的表现。
      RESTful 架构要求服务器端不保存有任何与特定 HTTP 请求相关的应用状态,所以应用状态必须由请求方在请求过程中提供,所以区分是否是 Stateless 可以根据请求状态信息由请求方还是响应方负责保存。
      在无状态模式下,客户端的多次请求并不都是同一个服务端来处理,更利于实现分布式系统的设计,也便于实现高可扩展和高可用性的服务端。
      思考: Cookie 的使用是否违背了 RESTful 原则?
    • 当 cookie 用作 session 会话的作用时候,是违背 RESTful 原则的,因为这时候服务端对请求状态做了保存和处理。
    • 当 cookie 用作身份认证,作为认证令牌等功能时候,是符合 RESTful 原则的,这时候是特定请求相关的状态信息,推荐使用 token 等认证机制。
  3. 可缓存性(Cacheability) :RESTful 提供的资源可以缓存,服务端需需要主动设置和回复相关资源的缓存信以及过期设置,缓存可以减少交互次数和降低接口时间。

  4. 统一接口(Uniform Interface):RESTful 遵循统一接口基本原则,规定了一批预定义的操作类型,不论什么资源,都是使用同类型的接口来对资源进行操作。HTTP 协议提供了诸如 GET,PUT,POST 等方法,RESTful 需要遵循这些方法的语义来对资源进行分类操作。分为四个子约束:

    • 每个资源都拥有一个资源标识
       {
           "url":"/books/1",
            ...
       }
    
    • 消息的自描述性
       HTTP 1.1 200 OK
       Content-Type : application/json
       Content-Length: 1024 
    
    • 资源的自描述性。
       {
           "name":"高等数学""price":20,
            ...
       }
    
    • HATEOAS Hypermedia As The Engine Of Application State(超媒体作为应用状态引擎)
      超媒体:当我们浏览网页时,可以随意从当前网页链接到另一个网页,可以无限链接下去,把一个个把资源链接起来。
        {
            "detail_url":"/infos/1",
            "recommends":[
                {
                    "url":"/books/2",
                    "detail_url":"/items/1",
                    ...
                }
            ],
            ...
        }
    

    客户端只可以通过服务端所返回各结果中所包含的信息来得到下一步操作所需要的信息,如到底是向哪个 URL 发送请求等。也就是说,一个典型的 REST 服务基本不需要额外的文档标示通过哪些 URL 访问特定类型的资源,而是通过服务端返回的响应来标示到底能在该资源上执行什么样的操作。

  5. 分层系统(Layered System):客户端无法直接知道连接的到终端还是中间设备,分层允许你灵活的部署服务端项目。

  6. 按需代码(Code-On-Demand,可选):服务端可以灵活的下发一些动态代码给客户端,比如js代码。

操作类型

RESTful 和其他类型 API 风格一个比较大的区别就是通过 HTTP 不同方法来代表不同的操作类型,而不是在 path 上面增加动词。

http方法操作说明资源操作幂等安全
GET查询操作SELECT
POST新增操作INSERT
PUT全量更新UPDATE
PATCH部分更新UPDATE
DELETE删除操作DELETE

HEAD,OPTIONS 等方法在 RESTful 中使用较少,这里就不再具体介绍。

值得注意的是,PUT 和 PATCH 均是更新资源的操作,区别在于 PUT 是全量更新,而 PATCH 是部分更新,同时,有的服务端当操作资源不存在时候,PUT 和 PATCH 也会做新增的兼容逻辑。

另外,为啥 PUT 是幂等的,而 PATCH 是非幂等呢?幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同,非幂等并不意味着它永远不会是幂等的,只是代表它不一定是幂等的。比资源属性中有个 update_count 这一个自增字段,当调用 PUT 时候,该字段由客户端自行传入,所以是幂等的,但是当 PATCH 更新其他字段时候,可能会影响到 update_count 这个字段的变化,所以这个时候是非幂等。

接口规范

状态码规范

RESTful 中,我们需要规范的使用状态码,这样才能客户端准确理解请求的最终结果,以便作出后续的处理。

比较常见的状态码如下:

200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405Method Not Allowed客户端请求中的方法被禁止
406Not Acceptable服务器无法根据客户端请求的内容特性完成请求
408Request Time-out服务器等待客户端发送的请求时间过长,超时
409Conflict服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理
415Unsupported Media Type服务器无法处理请求附带的媒体格式
416Requested range not satisfiable客户端请求的范围无效
417Expectation Failed服务器无法满足Expect的请求头信息
500Internal Server Error服务器内部错误,无法完成请求
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504Gateway Time-out充当网关或代理的服务器,未及时从远端服务器获取请求

值得注意是 RESTful 接口也可以是异步的,状态码202就是一种异步请求状态码。

URI规范

复数名词

普通接口风格

GET https://restful.com/api/getallUsers  获取所有用户
GET https://restful.com/api/getUser/10  获取标识为10用户信息

RESTful 风格

GET https://restful.com/api/users  获取所有用户
GET https://restful.com/api/users/1  获取标识为1用户信息

复数的使用不是强制规范,但是比如获取所有用户 GET /users,明显应该是复数,所以为了形式统一,建议都使用复数名词。

分隔符

使用分隔符来区分资源层。例如查询某公司某用户某电脑的信息时候:

GET https://restful.com/api/companies/1/users/1/computers/1

"/"不应该出现在 URL 的末尾。例如以下两个地址实际表示的都是同一个资源:

GET https://restful.com/api/users/1
GET https://restful.com/api/users/1/

RESTful 中 URI 资源具有唯一性,一个资源对应一个唯一的 URI,如果访问到末尾包含 "/" ,那么服务端应该301重定向到不带 "/"的地址。

query 过滤

有时候我们可以使用 query 来对资源层级进行优化。

例如获取被认证过的用户

GET https://restful.com/api/users/certified

可以优化成:

GET https://restful.com/api/users?certified=true

也可以做一些其他的过滤,例如:

GET https://restful.com/api/users?limit=10&sort=ASC

版本控制

在路径中加入版本号区分

GET https://restful.com/v1/api/users

我们也可以把版本号理解成资源的不同形式,这样也可以用同一个 URL,并通过头部,query 等信息区分。

以 github 为例,对于 github v3 版本,是通过 Header 头 Accept: application/vnd.github.v3 来做版本区分

响应规范

  • GET /users:返回用户资源的列表
  • GET /users/:id:返回单个用户资源
  • POST /users:返回新生成的用户资源
  • PUT /users/:id:返回完整的用户资源
  • PATCH /users/:id:返回完整的用户资源
  • DELETE /users/:id:返回一个空文档,或者状态码 204

请成功情况下,尽量使响应体纯净,最好只包含资源信息。但是一般实际中,还会在资源体外面嵌套一层基本数据机构,为了实现 RESTful 的 HATEOAS,往往还会在响应体中添加该资源对应的 link,当然,也可以将 link 置于资源体。

{
    "id":1,
    "name":"lyh",
    "desc":"大家一致认为他是美男子",
    ...
    "_link":{
      "xxxurl":"xxx"
    }
}

请求失败情况下,一般需要返回 code 和 message。

{
    "code":"",
    "message":""
}

其他

  • 不用大写字母,所有单词使用英文且小写。
  • 连字符用中杠"-"而不用下杠"_"
  • 可以用","或者";"来作为多个资源的分割

总结

符合 RESTFul 的接口表现:

  • 通过 http url 知道要什么资源
  • 通过 http method 知道对资源做什么
  • 通过 http status-code 知道对资源操作的结果

整体上看 RESTful 是个比较优秀的接口设计风格,接口规范,语义明确,目前也已广泛应用到后端的接口设计中,但是 RESTful 也存在不少的问题。

  • 过于理想化,实际开发中,不一定系统中的的东西都能很简单的映射成资源,比如 login/logout 就很难映射成资源。
  • 过于严苛的规则规范,使得后端开发者编写 HTTP API 痛苦。
  • 接口“资源化”,会导致有时候前端为了实现一些聚合功能,经常需要多次调用接口,使得前端性能受到一定的影响。