65.9K
CodeProject 正在变化。 阅读更多。
Home

纯 HTTP 并非完全 RESTful - 深入探讨 REST 架构风格以及 HTTP 如何显示协议的不足

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017 年 11 月 9 日

CPOL

4分钟阅读

viewsIcon

18111

本文试图将 REST 架构风格更深层次、更通用的本质映射到 HTTP 中,以揭示协议的不足。

引言

在我之前的 **文章**(请先阅读)中,我试图描述了我对 REST 架构风格的理解,而与任何通信协议无关。

2 REST 核心的映射

为了寻找通用性,我将 REST 的核心定义为一组概念,包括传输、状态、表示、安全性和幂等性。

使用表示形式传输状态可以轻松地映射到 HTTP。由于 HTTP 要求请求-响应通信模型,因此引入了描述命令的动词,以区分请求数据和提供数据的请求。这些动词是 GETPUT(或 POST)。这已经在一定程度上引入了 RPC 的概念,但尚未破坏 REST。HTTP 可以传输任何数据,因此应用程序可以自由地构建其表示形式。图 1 展示了一个使用 HTTP 和文本表示形式的音乐播放器示例。

图 1. 使用 HTTP 进行音乐播放器状态传输。

根据 HTTP 标准,GET 应为安全操作,PUT 应为幂等操作。如此说来,REST 的核心可以映射到 HTTP。

状态修补的映射

为了使用状态修补技术,这在状态通常很大的实际应用程序中是不可避免的优化,因此需要能够唯一标识状态块。HTTP 在此目的上使用 URI,将可寻址的状态块称为“资源”。图 2 展示了一个读取和写入状态块的示例。

图 2. 使用 URI 读取/写入状态块(资源)。

当以事务方式传输多个块时,就会出现问题。

事务性读取

为了以事务方式读取多个状态块(资源),客户端需要在单个自包含的消息中请求所有这些块,然后服务器在另一个自包含的消息中发送它们。只有当我们能够构建一个包含所有请求资源的 URL(例如,寻址一组资源)时,这才可能,因为 HTTP 本身不支持单个 GET 请求中的多个 URI(请参见图 3)。响应消息也存在类似的情况,因为在单个 HTTP 响应消息中没有办法将多个资源与其 URI 一起发送回客户端。可以使用一个 POST 请求,其具有一个与任何资源无关的任意 URI,以及一个包含所请求 URI 列表的有效负载。也可以伪造一个响应,其表示包含所有请求资源的列表及其 URI,但这种解决方案构成了新的“内部协议”,它仅仅将 HTTP 用作传输机制,从而破坏了分层隔离。

图 3. 纯粹的 HTTP 不支持读取/写入多个资源。

事务性写入

与事务性读取一样,纯粹的 HTTP 不支持对多个不相关状态块进行事务性写入。PUTPOST 请求都不支持单个 HTTP 请求消息中的多个 URI 和表示形式,也不支持单个 HTTP 响应消息中的多个状态码。确实,POST 支持多部分有效负载,但所有这些部分都指向单个 URI,因此这只是一个不令人满意的部分解决方案(尽管通过扩展 HTTP 添加非标准标头,可以使多部分消息的每个部分与 UIR 相关联)。

修补可变状态

在之前的文章中,我描述了如何通过引入表示状态块“时间线”的元数据来实现 RESTful 的可变状态修补。这只能部分地映射到 HTTP。NEWMODIFIED **形容词** 可以(在语义上不正确地)映射到 PUT **动词**,它根据目标资源是否存在而表现不同。MISSING **形容词** 可以映射到 DELETE **动词**。服务器应该以幂等的方式响应 PUTDELETE。问题在于,当修补多个不相关资源的任意状态,其中一些是 NEW,一些是 MODIFIED,另一些是 MISSING 时,HTTP 并不支持,因为单个消息只允许一个动词(请参见图 4)。

图 4. HTTP 不支持修补可变状态。

结论

HTTP 是一个很好的协议,能够很好地服务于其目的。它催生了 REST,并受到了 REST 的影响,但它并非 100% 符合 RESTful 规范(实际上它是一种 RPC,RESTful 行为可以建立在其之上)。人们试图通过设计巧妙的 URI 和表示形式来弥补这些不足,从而破坏分层隔离(有关一些技巧,请参见这本 ),但最终,纯粹的 100% 标准 HTTP 无法提供真正的 RESTful 通信,除非每次都传输“全部”状态。

附录:真正的 RESTful 协议应是什么样子

一个真正符合 RESTful 规范的协议,能够涵盖所有示例用例,可以使用任何它认为合适的传输方式,但应按照以下方式定义消息:

  • 消息类型:枚举(REQUESTDATAACKNOWLEDGEMENT)– 用于区分消息类型(并非每种消息类型都需要使用)
  • 对于 REQUEST 类型
    • 唯一块标识符列表:可以有一个特殊标识符表示“整个状态”,如“/”或 NULL;URI 是一个不错的选择,但并非唯一选择。
  • 对于 DATA 类型
    • 状态块列表
      • 对于每个块
        • 块标识符;
        • 块“时间戳”标签:枚举(NEWMODIFIEDMISSINGCURRENT);
        • 块表示:任何数据格式;
  • 对于 ACKNOWLEDGEMENT 类型
    • 成功/错误代码。

图 5 展示了一个拟议协议的消息交换示例。

图 5. RESTful 协议示例。
© . All rights reserved.