【问题标题】:Where in a HATEOAS architecture do you specify the HTTP verbs?您在 HATEOAS 架构中的哪个位置指定 HTTP 动词?
【发布时间】:2017-12-09 11:09:11
【问题描述】:

我正在阅读HATEOAS 上的article,虽然我理解为响应中的进一步操作提供 URL 的想法,但我看不到您在哪里指定应该使用哪些 HTTP 动词与这些 URL 交互。

例如来自What is HATEOAS and why is it important for my REST API?,如何从这个回复

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

您知道我是否应该向/account/12345/close 发送HTTP PUTPOST

【问题讨论】:

  • 你不应该使用你自己的动词。您应该在/account/12345 上发出DELETE,在/account/12345 上发出PUT 新总数,[或者更好的是POST/account/12345/operations,并带有适当的正文。
  • @moonwave99 - 但你怎么知道?您只是在做出与 API 实现者构建的内容可能一致或不一致的假设。

标签: rest hateoas


【解决方案1】:

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

要使用的动词由 HTTP 协议定义(例如 GETPOSTPUTOPTIONSDELETE 等)。 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 以更改它

PUT /accounts/12345/status

<?xml version="1.0"?>
<state>closed</state>

【讨论】:

  • 有一篇很好的文章 here 解释了为什么你会接受建模为资源的操作
  • 我真的不明白他们在那篇文章中所做的事情。他们说“如果你查阅状态图,你会发现所有各种可能的转换都没有在 API 契约中传达” 这是一件好事,资源可以有各种状态不应该在 URL 方案中。他们似乎这样做是为了避免为客户记录状态。这就是内容类型的用途。客户端和服务器都应该知道资源可以处于的有效状态。
  • 再次阅读这篇文章,在我看来,他们从根本上误解了 REST。他们似乎认为您必须提供资源作为触发器来移动服务器上的状态。所以要将audit 资源移动到accepted 的状态,你createaudits/1/acceptance 的新资源。不,您只需将audit/1 置于在客户端 的接受状态,然后将PUT 返回到服务器。您不(也不应该)构建 URL 端点来触发不同的状态转换。
  • restful objects 规范也支持它。我并不是说两者都是正确的,老实说,我对网络上众多不同的咒语感到困惑。该主题似乎几乎是那些选项卡与空格讨论之一。然而,对 url 中操作的批评似乎经常是 它不是 REST它是错误的。我会欢迎一篇文章深入解释为什么它是错误的。
  • Roy Fielding 提出 REST 概念的论文是了解 REST 大理念的良好开端。 audits/1/acceptance 之类的问题是这不必要地使资源模型复杂化。资源acceptance 现在是在语义上链接到audit 1 的单一状态的资源。它的存在仅作为动词来改变audit/1。改变一种资源有副作用也不错,但你不应该改变一种资源来改变另一种,你应该改变原来的资源。
【解决方案2】:

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

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

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

我倾向于设计提供这种方法的 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 方式编写,但这样做我使用的是响应正文,菲尔丁说这不是他的本意。

我会提供这个解释单独到答案:


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

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 我相信"REST Misconceptions" talk 他的意思是RDF 和都柏林核心。所以也许这个概念的动态应该包含在词汇表中?因此,如果您有一个没有透支的银行账户,那么您需要提供单独的条款,或者将要求纳入他们需要检查透支政策的词汇表中。
  • 我对 HATEOAS 的“媒体类型”方面做了一些思考,因为它做了一些简单的事情,比如翻阅结果表——这可以被认为是具有表示的信息系统的“主食”活动绑定到 URI。 HTML 提供了这种交互风格:将几个
【解决方案3】:

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

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

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

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

这里没有魔法。

【讨论】:

  • 啊,我以为它应该都是独立的。我会加粗它们是你答案的关键部分。
  • 我应该包含在 HTTP 协议和您使用的内容类型的文档中。它不应该被硬编码到 URI 中。例如,要编写一个网络浏览器,我只需要知道 HTTP 1.1 协议和 HTML。我不需要知道网站 www.nytimes.com 的具体布局,我可以从 HTTP 协议中弄清楚以及他们提供的 HTML 文档。如果您需要特定的内容类型,您可以使用 vnd.yourcompany.yourtype Content-Type 定义自己的内容类型,然后您可以为客户记录。
  • 我的意思是自包含的,因为响应也提供了有关如何使用 API 的足够信息。例如,如果您只能DELETE 帐户余额完全 0 您无法在响应中表明,因为您是 not 0是您可以在该 URL 上执行的其他动词,因此无论是否允许 DELETE,它都会以相同的方式列出。
  • HTTP 遵循“请求宽恕比请求许可更容易”的理念。不是事先知道你能做什么,而是尝试做一些事情并查看响应以找出它为什么不起作用。因此,如果您尝试删除一个帐户但由于余额不为 0 而无法删除它,则服务器应返回该信息。例如“401(未授权)您无权删除非空账户”。
  • 当然,没有什么可以阻止服务器为客户端提供管理资源,该资源仅包含当时特定资源上可能存在的链接。因此,如果您无法删除该帐户,那么 HTML 页面或 JSON 管理页面将不会在任何地方拥有该链接。但是您仍然必须假设有人可能会发送 HTTP 请求。服务器必须对允许或不允许请求承担全部责任。您不能依赖不会发送用户无法执行的请求的客户端。
【解决方案4】:

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

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

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

有关这些订单操作的信息将通过将操作名称以复数形式放在资源路径下,如果操作状态处于活动状态则返回详细信息/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'ing 请求这些 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}

【讨论】:

  • 虽然这可能有效,但它与 RESTful 架构的优势背道而驰,因为它将 URL 方案与资源可能所处的状态耦合得太紧。有效状态的定义应通过内容类型协商资源表示,而不是 URL 方案。这可能涉及定义客户端和服务器都知道的内容类型“订单”,但这超出了 URL 方案的范围。愚蠢的内容类型(例如只是“json”)会导致开发人员在 URL 中放置过多的表示逻辑。而是将其放在内容类型中。
  • @CormacMulhall 我很难理解自定义媒体类型如何传达动作的含义,您能否详细说明如何使用该方法摆脱 CRUD 陷阱?
  • 所以媒体类型不会告诉客户端它可以在服务器上执行什么操作。相反,客户端和服务器之间的合同是它们之间共享的资源的表示将采用这些格式。客户端不会询问服务器我可以对您执行什么操作,而是客户端按照内容类型规范将资源更新为有效的新状态,然后简单地将其放回服务器。因为它们都在处理相同的内容类型,所以服务器也应该理解这种新状态。
猜你喜欢
  • 2023-03-29
  • 2011-02-21
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 2021-10-30
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
相关资源
最近更新 更多