我读了一篇关于HAEOAS的文章,虽然我理解在响应中为进一步操作提供URL的想法,但我看不出你在哪里指定了应该使用哪些HTTP谓词来与这些URL交互。
例如,从What is HAEOAS and why it is 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
发出HTTPPUT
还是POST
吗?
不要在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>
您的问题在Stackloverlow上有很多答案,但其中大多数都回避了您提出的原因,我怀疑您总是会发现它们部分令人不满意。
如果我们相信Roy Fielding的话,那么使用HTTP/HTML将大多数商业交互式客户端应用程序编写为SOA RESTful/HATEOAS是不可能的。我不能说,在其他媒介中也有可能。
因此,实用的答案是">在文档中查找"one_answers">i中用该应用程序知识编写您的客户",附带帮助是">忽略我们这样做违反了Fielding规则的事实。"。
我倾向于设计提供这种方法的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说这不是他的意图。
我会提供这个解释单独的答案:
Fielding说:"很多人把任何基于HTTPs的接口称为REST API,这让我很沮丧。"(http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)。
请注意,他是如何如此尖锐地说">任何"基于HTTP的接口的。
他的"讲座"中最相关的部分是:
">在输入REST API时,除了初始URI(书签)和一组适合预期受众的标准化媒体类型(即,任何可能使用API的客户端都应理解)之外,不应事先了解任何内容从那时起,所有应用程序状态转换都必须由客户端选择服务器提供的选项来驱动,这些选项存在于所接收的表示中或由用户对这些表示的操纵所暗示。转换可以由客户端对媒体类型和资源通信机制的了解来确定(或受其限制),这两者都可以动态地改进(例如,按需编码)。[此处的失败意味着带外信息正在驱动交互,而不是超文本。]">
他这么说是因为HTTP/HTML应用程序URI媒体类型只是"text/HTML",其中的动词在哪里?没有。URI不能告诉您使用/导航需要什么谓词,因此您不能仅使用带内数据在客户端中动态构建"下一个"导航。
他解释说,他相信我们将URI作为CDATA的一部分来呈现,CDATA包括"方法",或者URI的上下文会像FORM元素一样明显地提供它。他明确谴责OpenSocialst REST API,指出它不是RESTful。
这里:">具有href属性的锚元素创建一个超文本链接,当选择该链接时,会调用与CDATA编码的href属性对应的URI上的检索请求(GET)。"标识符、方法和媒体类型是正交的,媒体类型不赋予方法意义。相反,媒体类型告诉客户端使用什么方法(例如,anchor表示GET)或如何确定要使用的方法(例如表单元素表示查找方法属性)。客户端应该已经知道这些方法的含义(它们是通用的)以及如何取消引用URI。
请注意,他说客户应该已经知道方法的含义,而不是说客户应该知道是什么——这就是你提出问题的原因。很多人都在为此而挣扎,因为事实上,在大多数SOA环境中,我们并没有像这样构建应用程序。
像许多工程师一样,我只希望菲尔丁能澄清或重新陈述,但他不仅没有这样做,他还发表了两条对我们工程师的警告,加倍强调他的声明,说我们应该停止称API为RESTful,并接受我们正在构建RPC。
我认为类似JSON元素的方法是一个合理的桥梁,但我无法回答这样一个事实,即我们使用请求主体来完成它,而不是依赖媒体类型来暗示它
最后,HTTP中有一个更新的谓词OPTIONS,对于给定的URI,将返回允许的谓词操作列表。我认为Fielding参与了这个HTTP修订版的编写。这将允许客户端在没有被禁止的内部应用程序知识的情况下通用地构建URI导航。但在现实世界中,我能想到三个问题:
- 您必须在服务聚合中编码一种机制,以便对您试图返回的每个URI进行调用,而且由于许多数据包含许多URI(HAL中的_link),这会在服务响应结构中添加大量额外的"跃点"。我们可能都会抱怨
- 实际上,没有一个声称是RESTful的SOA站点实际实现了OPTIONS谓词方法调用,无论如何都可以使用它进行查询
- 我们都会抱怨它给客户的处理增加了"不必要的"额外电话(尤其是在电子商务领域),并倾向于将我们推到SLA要求之外
您知道是应该PUT还是POST到/account/12345/close吗?
您可以查阅API的文档,这就是您所知道的。HAEOS不能替代正式文档。与任何其他API一样,RESTAPI需要文档。
HAEOS可以让您了解特定资源的其他选项。它不会告诉你为什么要使用这些选项,也不会告诉你要向它们发送什么信息。内容类型只表达语法和高级语义,而不是应用程序级语义,因此它们也不是文档。
如果您想知道如何使用REST API,请阅读文档。如果您希望其他人使用您的REST API,请为他们提供文档
这里没有魔法。
@Cormac Mulhall的回答很好,但我想建议一个改进,我从一位同事那里听到:
发生在资源上的动作或事件可以被视为子域名词,使用动作动词或事件名称的动名词形式,但应该放在有意义的路径标识符下,如"动作"或"事件"或类似的东西。将返回的资源表示表示有关该操作的状态数据,因此POST
或PUT
作为请求进行操作。
假设订单有几个生命周期状态。在起草后的某个时刻,订单被下达、履行或取消。
如果动作状态是活动的,则通过将动作名称以复数名词形式放在具有/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
当这些操作是幂等的(一个顺序不能在一行中放置两次)时,因此可以通过对这些URI的适当表示的PUT
请求这些操作。当它们不是幂等的时,它们是由POST
生成的复数形式。
例如,为了跟踪批准订单,我们可以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}