REST - Representational State Transfer

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设计:

// 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. 参考