27

I was reading an article on HATEOAS and while I understand the idea of providing the URLs for further actions in the response, I don't see where you specify what HTTP verbs should usedto interact with those URLs.

For example, from What is HATEOAS and why is it important for my REST API?, how from this response

GET /account/12345 HTTP/1.1

HTTP/1.1 200 OK
<?xml version="1.0"?>
<account>
    <account_number>12345</account_number>
    <balance currency="usd">100.00</balance>
    <link rel="deposit" href="/account/12345/deposit" />
    <link rel="withdraw" href="/account/12345/withdraw" />
    <link rel="transfer" href="/account/12345/transfer" />
    <link rel="close" href="/account/12345/close" />
</account

do you know if I should issue an HTTP PUT or POST to /account/12345/close?

4

4 回答 4

34

不要将动词放在你的 URI 中(例如 /account/12345/ transfer)。URI 代表资源,而不是操作。

要使用的动词由 HTTP 协议定义(例如GET, POST, PUT, OPTIONS,DELETE等)。REST 是具有一组约束的架构设计,而 HTTP 是遵守这些约束的协议。HTTP 定义了一组有限的动词来将资源的状态从客户端传输到服务器,反之亦然。根据定义,您只能使用这些动词。

客户端应该根据它试图做什么来决定使用什么 HTTP 动词。服务器不需要告诉它有哪些动词,它已经基于 HTTP 协议知道了。

如果客户端需要知道它可以在资源上使用哪些动词,它可以使用 OPTIONS 动词查询资源并查看Allow响应中的标头(假设服务器返回此信息,如果有帮助,它应该返回此信息)。一些资源可能只接受 GET,而另一些可能接受其他资源,例如 POST 和 PUT。

查看 HTTP 规范,了解在什么上下文中使用什么动词。

从您的原始帖子中举一个例子。假设您有一个帐户资源,其 URI 位于

/accounts/12345

并且您想关闭该帐户。记住 REST 是状态转移。客户正在关闭账户,因此它的账户最终处于关闭状态。然后它将该状态传输到服务器,以便客户端和服务器都彼此一致。因此,您PUT将客户端状态(即处于关闭状态的资源)放到服务器上

PUT /accounts/12345

请求的主体应包含处于关闭状态的资源的表示。假设您使用 XML 来表示帐户资源,它将是这样的

PUT /accounts/12345

<?xml version="1.0"?>
<account>
    <account_number>12345</account_number>
    <balance currency="usd">100.00</balance>
    <state>closed</state>
</account>

服务器上的资源现在镜像客户端上的资源。两者都处于关闭状态。如果您不想在每次更改其属性之一时传输整个资源,您可以将它们拆分为资源层次结构。使帐户的状态成为自己的资源,然后将其放入以更改它

PUT /accounts/12345/status

<?xml version="1.0"?>
<state>closed</state>
于 2013-11-13T17:10:28.120 回答
13

您的问题在 Stackoverlow 上有很多答案,但大多数都避开了您所问的原因,而且我怀疑您总是觉得它们部分不满意。

如果我们相信 Roy Fielding 的话,那么使用 HTTP/HTML 将大多数商业交互式客户端应用程序编写为 SOA RESTful/HATEOAS 是不可能的。我不能说,这可能在其他媒体中是可能的。

所以实际的答案是“在文档中查找它”和“用它中的应用程序知识编写你的客户”,并附带“忽略我们这样做违反菲尔丁规则的事实”。

我倾向于设计提供这种方法的 JSON 响应:

GET /account/12345 HTTP/1.1
{
    "account": {
        "number": "12345",
        "currency": "usd",
        "balance": "100.00",
        "deposit": {
            "href": "/account/12345/deposit",
            "action": "POST"
        },
        "withdraw": {
            "href": "/account/12345/withdraw",
            "action": "POST"
        },
        "transfer": {
            "href": "/account/12345/transfer",
            "action": "POST"
        },
        "close": {
            "href": "/account/12345/close",
            "action": "DELETE"
        }
    }
}

...根据需要为设计添加其他属性,但这些是基础。

我相信这允许以 RESTful 方式编写消费客户端,但这样做我使用的是响应体,菲尔丁说这不是他的本意。

不过,我会单独提供这个解释:


菲尔丁说:“我对将任何基于 HTTP 的接口称为 REST API 的人数感到沮丧。” (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)。

请注意他如何如此刺耳地说“任何”基于 HTTP 的接口。

他的“讲座”中最相关的部分是:

"除了初始 URI(书签)和适合目标受众的标准化媒体类型集(即,任何可能使用该 API 的客户端都可以理解)之外,应该在没有任何先验知识的情况下输入 REST API 。由此点上,所有应用程序状态转换必须由客户端选择服务器提供的选项来驱动,这些选项存在于接收到的表示中或由用户对这些表示的操作暗示。转换可以由客户端对媒体的了解来确定(或限制)类型和资源通信机制,两者都可以即时改进(例如,按需编码)。[此处的失败意味着带外信息正在驱动交互而不是超文本。]

他这样说是因为 HTTP/HTML 应用程序 URI媒体类型只是“text/html”,而其中的动词在哪里?没有一个。URI 无法告诉您使用/导航需要什么动词,因此您不能仅使用带内数据在客户端中动态构建“下一个”导航。

他解释说,他相信我们将 URI 呈现为包含“方法”的 CDATA 的一部分,或者 URI 的上下文会不言而喻地提供它,就像 FORM 元素一样。他明确反对 OpenSocialst REST API,声称它不是 RESTful。

这里:“带有HREF属性的<em>锚元素创建一个超文本链接,选择后,在与CDATA编码的HREF属性相对应的URI上调用检索请求(GET)。” 标识符、方法和媒体类型是正交关注点——媒体类型没有赋予方法意义。相反,媒体类型告诉客户端使用什么方法(例如,anchor 意味着 GET)或如何确定要使用的方法(例如,表单元素说要查看方法属性)。客户端应该已经知道方法的含义(它们是通用的)以及如何取消引用 URI。

请注意,他说客户应该已经知道这些方法的含义,他并没有说客户应该已经知道它们什么——这就是您提出问题的原因。很多人都在为此苦苦挣扎,因为我们实际上并没有在大多数 SOA 环境中像这样构建我们的应用程序。

像很多工程师一样,我只是希望菲尔丁能做出澄清或重新声明,但他不仅没有这样做,他还向我们作为工程师发表了两条进一步的警告,加倍强调他的声明,说我们应该停止调用 API 的 RESTful 并接受我们正在构建 RPC。

我认为类似 JSON 元素的方法是一个合理的桥梁,但我无法回答我们使用请求正文来执行此操作的事实,而不是依赖媒体类型来暗示它。

最后,HTTP 中有一个较新的动词,称为 OPTIONS,对于给定的 URI,它将返回允许的动词动作列表。我认为菲尔丁参与编写了这个 HTTP 修订版。这将允许客户端在没有被禁止的内部应用知识的情况下一般地构建 URI 导航。但这在现实世界中我能想到三个问题:

  1. 您必须在您的服务聚合中编写一种机制,以便对您尝试返回的每个 URI 进行调用,并且由于大量数据包含许多 URI(HAL 中的 _links),这会在您的服务响应构造中添加大量额外的“跃点”。我们可能都会抱怨这一点。
  2. 几乎没有声称是 RESTful 的 SOA 站点实际上实现了一个 OPTIONS 动词方法调用,让您无论如何都可以进行此查询。
  3. 我们都会抱怨它为客户的处理增加了“不必要的”额外调用(尤其是在电子商务世界中),以及它倾向于将我们推到 SLA 要求之外。

于 2017-12-09T01:21:54.593 回答
7

你知道你应该PUT还是POST到/account/12345/close?

您查阅 API 的文档,这就是您所知道的。HATEOS 不是正式文档的替代品。与任何其他 API 一样,REST API 需要文档。

HATEOS 让您知道您的其他选项来自特定资源。它没有告诉您为什么要使用这些选项,或者您将向它们发送什么信息。内容类型仅表达语法和高级语义,而不是应用程序级语义,因此它们也不是文档。

如果您想知道如何使用 REST API,请阅读文档。如果您希望其他人使用您的 REST API,请向他们提供文档。

这里没有魔法。

于 2013-11-13T17:34:45.323 回答
4

@Cormac Mulhall 的回答非常好,但我想建议我从一位同事那里听到的改进:

发生在资源上的动作或事件可以被视为从属领域名词,使用动作动词的动名词形式或事件名称,但应放在有意义的路径标识符下,例如“动作”或“事件”或类似的东西. 将返回的资源表示表示有关操作的状态数据,因此POSTorPUT作为请求操作。

假设订单有多个生命周期状态。在起草后的某个时间点,下达、履行或取消订单。

有关这些订单操作的信息将通过将操作名称以复数形式放在资源路径下来定位,/actions如果操作状态是活动的,则返回详细信息,404 NOT FOUND否则。

https://order.api.foobar.com/v1.0/orders/{orderId}/actions/placements
https://order.api.foobar.com/v1.0/orders/{orderId}/actions/fulfillments
https://order.api.foobar.com/v1.0/orders/{orderId}/actions/cancellations

当这些动作是幂等的(订单不能连续放置两次)时,可以通过PUT对这些 URI 的适当表示来请求这些动作。当它们不是幂等的时,它们是通过POST'ing 到复数形式来创建的。

例如,要跟踪批准订单,我们可以POST

https://order.api.foobar.com/v1.0/orders/{orderId}/approvals

然后我们通过执行 GET 来查看有关个人批准的信息:

https://order.api.foobar.com/v1.0/orders/{orderId}/approval/1

使用称为“动作”之类的聚合来查找所有动作通常很有用:

https://order.api.foobar.com/v1.0/orders/{orderId}/actions

我们可以POST这样做,让表示声明意味着什么类型的动作。

{orderId}您还可以通过关闭参数来获取单个订单的操作列表:

https://order.api.foobar.com/v1.0/orders/actions/placements
https://order.api.foobar.com/v1.0/orders/actions

这些可以通过添加查询参数来搜索:

https://order.api.foobar.com/v1.0/orders/actions/placements?since={sinceTimestamp}
于 2015-04-01T20:52:38.557 回答