REST - Representational State Transfer

AI 摘要: 本文是关于Restful架构的简要介绍,Restful架构是一种符合REST原则的架构,它将资源、表现层和状态转移相结合。文章重点讲解了REST的概念、Restful架构的六种约束,以及一些设计提示。Restful架构的设计需要命名资源、利用HTTP动词表述请求的动作、提供合理的资源URI名称、使用HTTP响应代码指示状态和控制资源的粒度。此外,文章也强调了幂等性在Restful架构中的重要性。

Restful 架构,即符合了 REST(Representational State Transfer)原则的架构;REST 翻译为表现层状态转移,加上主语“资源 Resource”,综合理解下来就是资源表现层/表征状态转移

1. REST

1.1. Resource - 资源

互联网操作的实体对象皆为资源,无论是 HTML、图片、音频、视频等超媒体信息,亦或是服务端提供的服务,都可以理解为资源,同时我们使用 URI(统一资源定位符)指向它。

资源的操作,就是指通过 HTTP 协议,操作调用它的 URI。

1.2. Representational - 表现层

不同的资源可以以不同的形式表现出来,比如文本以 HTML、XML、JSON、二进制形式,图片以及不同的编码格式 PNG、JPEG 等表现出来。

  • URI 只代表资源的实体,不代表它的形式
  • URL 统一资源定位,表示如何定位操作或获取该资源

所以,https://a.com/user.html表示 URL,但https://a.com/user表示 URI。.html后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,应该基于 HTTP 协议请求头的Accept以及响应头的Content-Type来指明。

1.3. State transfer - 状态转移

HTTP 超文本传输协议,是一个客户端(用户)和服务端(网站)之间请求和应答的标准,HTTP 协议本身是无状态的,但操作资源的主体(人或物)通常是需要对资源数据有差异的,势必涉及到数据和状态的变化,因此需要通过某种手段,让服务器端发生"状态转化"(State Transfer)。

客户端依托的手段仅能通过 HTTP 协议来操作资源:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

综合起来,RESTful 架构可以理解为:

  1. 每一个 URI 代表一种资源
  2. 客户端通过 HTTP 协议进行服务端的 URI 资源的操作,进行服务端资源的表现传递

2. REST 架构风格描述了六种约束

2.1. 统一接口约束

  1. URI 作为资源标识符在请求中标识各个资源
  2. 客户可以通过资源表现操纵资源
  3. 每条消息都包含足够的信息来描述如何处理消息(MIME、缓存处理)
  4. 超媒体作为应用程序状态引擎(HATEOAS)

2.2. 无状态

这意味着处理请求的必要状态包含在请求本身中,在服务器进行处理之后,状态和响应主体将适当的状态或重要状态的片段传送回客户端。

“会话”的概念,它在多个 HTTP 请求中维护状态,在 REST 中,客户端必须包含服务器的所有信息以完成请求,如果该状态必须跨越多个请求,则根据需要重新发送状态。

无状态可以实现更高的可伸缩性,因为服务器不必维护,更新或传达该会话状态,此外,负载均衡器不必担心无状态系统的会话亲和性(代理或网关不用理会会话粘性)。

2.3. 可缓存

客户端在资源请求获取响应后,可以缓存响应。因此,响应必须隐式或显式地将自身定义为可缓存或不可缓存,以防止客户端重用陈旧或不适当的数据以响应进一步的请求。

管理良好的缓存部分或完全消除了一些客户端-服务器交互,进一步提高了可伸缩性和性能。

2.4. 客户端服务端分离

客户端与服务器通过资源接口分离,客户端不关心数据存储,数据存储仍然是每个服务器的内部,从而提高了客户端代码的可移植性。

服务器不关心用户界面(App 或 Web)或用户状态,因此服务器可以更简单,更具可伸缩性,只要不改变接口,服务器和客户端也可以独立替换和开发。

2.5. 系统分层

客户端通常无法判断它是直接连接到最终的服务器,还是沿途的中间缓存代理,中间服务器可以通过启用负载平衡和提供共享缓存来提高系统可伸缩性,同时也可以实施安全策略。

2.6. 按需扩展客户端功能(可选)

服务器能够通过向客户端传输可以执行的逻辑来临时扩展或自定义客户端的功能,比如 Java 小程序、JS 脚本,以及最近很流行的webassembly

遵循符合 REST 架构风格,将容易获取到分布式系统的常见优势:性能,可伸缩性,简单性,可修改性,可见性,可移植性和可靠性。

如果服务违反按需扩展客户端功能外的任何其他约束,则不能严格地将其称为 RESTful。

3. Restful 架构提示

3.1. 命名资源

除了适当地使用 HTTP 动词之外,在创建易于理解,易于利用的 Web 服务 API 时,资源命名可以说是最有争议和最重要的概念。当资源命名良好时,API 直观且易于使用。做得不好,相同的 API 可能会感觉难以使用和理解。

在决定系统中的资源时,将它们命名为名词而不是动词或动作。换句话说,RESTful URI 应该引用一个资源而不是引用一个动作,名词具有动词不具有的属性,只是另一个区别因素。

RESTful API 是为消费者编写的,URI 的名称和结构应该向这些消费者传达意义,为您的客户而不是为您的数据设计 API 资源。

一些反模式 API 设计:

1
2
3
4
5
6
7
8
// URI中含动词或者使用GET做资源更新
GET http://api.example.com/services?op=update_customer&id=12345&format=json
GET http://api.example.com/update_customer/12345
GET http://api.example.com/customers/12345/update
// URI中含版本
GET http://www.example.com/app/1.0/foo
GET http://www.example.com/app/1.1/foo
GET http://www.example.com/app/2.0/foo

参见一些其他优秀 API 的设计:

3.2. 利用 HTTP 动词表述请求的动作

  • GET:读取特定资源(通过标识符)或资源集合。
    • GET(和 HEAD)是幂等的,这意味着生成多个相同的请求最终会产生与单个请求相同的结果。
    • 不要通过 GET 公开不安全的操作 - 它永远不应该修改服务器上的任何资源
  • PUT:更新特定资源(通过标识符)或资源集合,如果资源标识符是事先已知的,也可以用于创建特定资源。
    • PUT 不是一个安全的操作,因为它修改(或创建)服务器上的状态,但它是幂等的。换句话说,如果您使用 PUT 创建或更新资源,然后再次进行相同的调用,则资源仍然存在,并且仍然具有与第一次调用时相同的状态,强烈建议对非幂等请求使用 POST。
  • DELETE:通过标识符删除/删除特定资源。
    • DELETE 操作是幂等的,强烈建议对非幂等请求使用 POST。
  • POST:创建一个新资源。对于不适合其他类别的操作,也是一个包罗万象的动词。
    • POST 既不安全也不是幂等。因此,建议用于非幂等资源请求。制作两个相同的 POST 请求最有可能导致两个资源包含相同的信息。

GET 请求不得更改任何底层资源数据,可能仍会发生更新数据的测量和跟踪,但 URI 标识的资源数据不应更改。

更多 HTTP 方法参考:https://www.restapitutorial.com/lessons/httpmethods.html

3.3. 提供合理的资源 URI 名称

制作出色的 API 是 80%的艺术和 20%的科学,以下是一些 URI(资源名称)设计的快速规则:

  1. 使用标识符而不是在查询字符串中
    • Good: /users/12345
    • Poor: /api?type=user&id=23
  2. 使用 URL 的分层特性来暗示结构
  3. 使用 HTTP 方法指定请求的谓词部分,资源名称应为名词,避免使用动词作为资源名称,以提高清晰度。
  4. 使用使用复数形式隐喻使用集合,避免在 URL 中使用集合措辞(customers vs. customer_list)
  5. 使用小写,用下划线(_)或连字符(-)分隔单词
  6. 为客户而不是为服务的数据设计
  7. 保持网址尽可能短

常见误区:

  • URI 包含动词,URI 代表资源实体,应该用名词,操作动作利用 HTTP 动词表述
  • URI 中加入版本,应该放到 HTTP 请求头中:Accept: vnd.example-com.foo+json; version=1.0

3.4. 使用 HTTP 响应代码指示状态

响应状态代码是 HTTP 规范的一部分,它们中有很多可以解决最常见的情况。本着使 RESTful 服务包含 HTTP 规范的精神,我们的 Web API 应该返回相关的 HTTP 状态代码。例如,当成功创建资源时(例如,来自 POST 请求),API 应返回 HTTP 状态代码 201

更多状态码参考:https://www.restapitutorial.com/httpstatuscodes.html#

3.5. 资源粒度控制

在开始时,最好创建模仿系统的底层应用程序域或数据库体系结构的 API,最终将需要使用多个底层资源的聚合服务来减少干扰,但是,稍后从单个资源创建更大的资源比从较大的聚合创建细粒度或单个资源要容易得多。

因此,从易于定义的小型资源开始,为这些资源提供 CRUD 功能,稍后创建那些面向用例,减少 chattiness 的资源。

4. 幂等 - idempotency

从 RESTful 服务的角度来看,要使操作(或服务调用)具有幂等性,客户端可以在产生相同结果的同时重复进行相同的调用。换句话说,发出多个相同的请求与发出单个请求具有相同的效果。请注意,虽然幂等操作在服务器上产生相同的结果(没有副作用),但响应本身可能不相同(例如,资源的状态可能在请求之间发生变化)。

GET,HEAD,OPTIONS 和 TRACE 方法被定义为安全的,这意味着它们仅用于检索数据。这使得它们也是幂等的,因为多个相同的请求将表现相同。

PUT 和 DELETE 方法被定义为幂等的,但是,DELETE 有一个警告。DELETE 的问题在于,如果成功通常会返回 200(OK)或 204(No Content),则通常会在后续调用中返回 404(Not Found),除非服务被配置为“标记”资源以便删除删除它们(软删除)。但是,当服务实际删除资源时,下一个调用将找不到要删除它的资源并返回 404,但是,每次 DELETE 调用后服务器上的状态都相同,但响应不同。

POST 既不安全也不是幂等的,两个相同的 POST 请求最有可能导致两个资源包含相同的信息。换句话说,在创建新资源时,对服务的 POST 将新资源与父资源相关联,分配 ID(新资源 URI)等。

5. 总结

客户端基于 HTTP 协议的相关动词(GET、POST 等)以及头部信息(比如类型、版本、缓存控制等),通过代表资源 Resrouce 的 URI,与服务端进行资源的操作,操作的过程涉及到资源的表现层状态转移。

Restful 架构的 API 设计,需要更多考虑 API 消费者的使用,前期基于一些小的独立的资源 API,后期提供一些资源聚合的 API,相对来说更容易操作。

在设计 Restful 架构过程中,URI 中不应该含带动词,以及通过复数表述资源集合,通过 HTTP 状态码来做资源操作的结果指示。

针对幂等性,在 HTTP 方法中,GET、HEAD、OPTION、TRACE 是安全且幂等的,PUT 和 DELETE 是幂等但有警告的,POST 是不安全且非幂等。

总的来说,设计一类好的 Restful 更多的是艺术工作,需要遵循特定的 REST 规约,并站在资源以及 API 消费者的角度考虑和衡量。

6. 参考