以RESTful方式在资源上调用服务器端方法


142

请记住,我对REST有基本的了解。假设我有以下网址:

http://api.animals.com/v1/dogs/1/

现在,我想让服务器将狗叫起来。只有服务器知道如何执行此操作。假设我想让它在CRON作业上运行,该作业会使狗在以后的永恒中每10分钟吠叫一次。这个电话看起来像什么?我有点想这样做:

网址请求:

ACTION http://api.animals.com/v1/dogs/1/

在请求正文中:

{"action":"bark"}

在为构造自己的HTTP方法而生我的气之前,请帮助我,让我更好地了解如何以RESTful方式调用服务器端方法。:)

编辑澄清

有关“树皮”方法功能的更多说明。以下是一些可能导致结构不同的API调用的选项:

  1. 树皮只会向dog.email发送电子邮件,却什么也没记录。
  2. bark将电子邮件发送到dog.email,并将dog.barkCount递增1。
  3. 树皮会在树皮发生时使用bark.timestamp记录创建一个新的“树皮”记录。它还会将dog.barkCount递增1。
  4. 吠声运行系统命令以从Github中获取最新版本的狗码。然后,它将一条文本消息发送到dog.owner,告诉他们新的狗代码正在生产中。

14
有趣的是,添加赏金似乎吸引的答案比您原来的要差;-)在评估答案时,请记住:1)HTTP动词的规范排除了POST之外的任何选择。2)REST与URL结构无关–它是约束的通用列表(无状态,可缓存,分层,统一的接口等),而不是带来好处(可伸缩性,可靠性,可见性等)。3)当前的做法(例如在RPC规范中使用POST)胜过定义自己的API规则的定义主义者。4)REST需要一个统一的接口(遵循HTTP规范)。
Raymond Hettinger 2013年

@Kirk您对新答案有何想法?您还有什么想知道但没有解决的吗?如果能进一步帮助我,我很乐意再次编辑我的答案。
约旦

@RaymondHettinger PATCH可能合适。我解释了为什么在我回答的末尾。
约旦

PATCH仅适合将dog.barkCount递增1。POST是发送电子邮件,创建新的树皮记录,运行要从Github下载的命令或触发文本消息的方法。@Jordan,您对PATCH RFC的阅读是富有想象力的,但其意图是作为部分资源修改的PUT的变体而有所不同。我认为您不是通过提供HTTP规范的非常规读物来帮助OP,而不是承认使用POST进行远程过程调用的标准做法。
Raymond Hettinger 2013年

@RaymondHettinger的实践事实上使 POST标准化了吗?我见过的所有标准RPC接口都是按实体(不是RESTful)和URI标识资源的,因此优先考虑RPC约定的有效答案无论如何都必须是非常规的,我认为这反驳了常规RPC的价值:一个是有想象力的或不一致的。POST 绝对不会出错,因为POST是数据处理的全部,但是还有更多特定的方法。REST意味着命名资源并描述其状态的变化,而不是命名状态更改过程。PATCH和POST都描述状态变化。
约旦

Answers:


280

为什么要针对RESTful设计?

RESTful原理为Web服务API设计带来了使网站易于使用(使随机的人类用户可以“浏览”它们)的功能,因此程序员易于使用。REST不好,因为它是REST,它很好,因为它很好。它之所以好,主要是因为它很简单

普通的HTTP(没有SOAP信封和单个URI重载POST服务)的简单性(有些人称之为“功能不足”)实际上是其最大的优势。HTTP立即要求您具有可寻址性无状态性:这两个基本设计决策使HTTP可以扩展到当今的大型站点(和大型服务)。

但是REST并不是灵丹妙药:有时RPC样式(“远程过程调用”,例如SOAP)可能是合适的,有时其他需求优先于Web的优点。这可以。我们真正不喜欢的是不必要的复杂性。程序员或公司通常会引入RPC样式的服务来完成普通的旧HTTP可以很好处理的工作。结果是,HTTP简化为用于巨大XML有效负载的传输协议,该XML有效负载解释了“真正”发生的事情(不是URI或HTTP方法提供了线索)。最终的服务太复杂了,无法调试,除非您的客户端具有开发人员想要的确切设置,否则它将无法工作。

同样的方式的Java / C#代码可以面向对象,只是使用HTTP不会使设计REST风格。人们可能会急于按照应调用的操作和远程方法考虑其服务。难怪这将最终以RPC样式服务(或REST-RPC混合)结束。第一步是要有不同的想法。RESTful设计可以通过多种方式实现,一种方式是根据资源而非操作考虑应用程序:

💡无需考虑可以执行的动作(“搜索地图上的位置”)...

...尝试根据这些操作的结果进行思考(“地图上符合搜索条件的地点列表”)。

我将在下面举例。(REST的其他关键方面是HATEOAS的使用-我在这里不作介绍,但是我在另一篇文章中很快谈到了它。)


第一个设计的问题

让我们看一下建议的设计:

ACTION http://api.animals.com/v1/dogs/1/

首先,我们不应该考虑创建新的HTTP动词ACTION)。一般来说,由于以下几个原因,这是不可取的:

  • (1)仅给出服务URI,“随机”程序员将如何知道该ACTION动词的存在?
  • (2)如果程序员知道它的存在,他将如何知道它的语义?这个动词是什么意思?
  • (3)一个人期望该动词具有什么特性(安全性,幂等性)?
  • (4)如果程序员拥有一个仅处理标准HTTP动词的非常简单的客户端,该怎么办?
  • (5) ...

现在让我们考虑使用POST(我将在下面讨论原因,现在请相信我):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

可能是好的...但只有当

  • {"action":"bark"}是一个文件;和
  • /v1/dogs/1/是一个“文档处理器”(类似于工厂)的URI。“文档处理器”是一个URI,您只需将其“扔给”并“忘记”它们-在“扔”之后,处理器可能会将您重定向到新创建的资源。例如,用于在消息代理服务中发布消息的URI,在发布后,该服务会将您重定向到显示消息处理状态的URI。

我对您的系统了解不多,但我敢打赌,两者都不对:

  • {"action":"bark"} 不是文档,实际上您尝试忍者偷窥的方法服务的方法;和
  • /v1/dogs/1/URI表示“狗”资源(可能是狗id==1),而不是一个文档处理器。

因此,我们现在所知道的是,上面的设计不是那么RESTful,但这到底是什么?有什么不好的呢?基本上,这是不好的,因为那是具有复杂含义的复杂URI。您无法从中推断任何内容。程序员如何知道狗的bark动作可以秘密注入POST


设计问题的API调用

因此,让我们开始追逐,并尝试通过考虑资源来 REST式设计那些树皮。请允许我引用Restful Web Services一书:

一个POST请求是试图从现有的一个新的资源。从数据结构的角度来看,现有资源可能是新资源的父级,树的根就是其所有叶节点的父级。或者现有资源可以是特殊的“工厂” 资源,其唯一目的是生成其他资源。与POST请求一起发送的表示描述了新资源的初始状态。与PUT一样,POST请求根本不需要包含表示。

根据上面的描述,我们可以看到bark可以将其建模为a的子资源dog(因为a bark包含在狗中,也就是说,树皮被“吠叫”了)狗)。

通过这种推理,我们已经得到:

  • 方法是 POST
  • 资源是/barksdog:的子资源/v1/dogs/1/barks,代表bark“工厂”。该URI对于每条狗来说都是唯一的(因为它位于下方/v1/dogs/{id})。

现在,列表中的每种情况都有特定的行为。

1.树皮只是向发送一封电子邮件,dog.email什么也不记录。

首先,吠叫(发送电子邮件)是同步任务还是异步任务?其次,bark请求是否需要任何文件(可能是电子邮件)还是空的?


1.1树皮向发送电子邮件且不dog.email记录任何内容(作为同步任务)

这种情况很简单。调用barks工厂资源会立即发出吠声(发送电子邮件),并立即给出响应(如果确定):

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

它记录(更改)没有任何内容200 OK就足够了。它表明一切都按预期进行。


1.2树皮向发送电子邮件且不dog.email记录任何内容(作为异步任务)

在这种情况下,客户端必须有一种跟踪bark任务的方法。bark然后,该任务应该是具有自己的URI的资源:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

这样,每个bark都是可追溯的。然后,客户端可以GETbarkURI 发出A ,以了解其当前状态。甚至可以使用a DELETE取消它。


2.树皮向发送电子邮件 dog.email,然后增加dog.barkCount1

如果要让客户端知道dog资源已更改,则此操作可能会比较棘手:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

在这种情况下,location标题的目的是让客户知道他应该看一下dog。从HTTP RFC关于303

存在此方法主要是为了允许POST激活脚本的输出 将用户代理重定向到所选资源。

如果任务是异步的,bark则就像1.2情况一样需要一个子资源,并且303应该GET .../barks/Y在任务完成时在a返回该资源。


3.树皮会创建新的记录,并在树皮发生时bark进行bark.timestamp记录。它也增加dog.barkCount1。

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

在这里,bark是由于请求而创建的,因此将201 Created应用状态。

如果创建是异步的,202 Accepted则需要a(如HTTP RFC所述)。

保存的时间戳是bark资源的一部分,可以通过对其进行检索GET。更新的狗也可以在其中“记录” GET dogs/X/barks/Y


4. bark运行系统命令以从Github中下载最新版本的狗码。然后,它发送一条短信dog.owner告诉他们新的狗码正在生产中。

这句话的措词很复杂,但这几乎是一个简单的异步任务:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

然后,客户端将发出GETs以/v1/dogs/1/barks/a65h44了解当前状态(如果将代码拉出,则将电子邮件发送给所有者等)。每当狗改变时,a 303都是适用的。


包起来

引用罗伊·菲尔丁Roy Fielding)

REST对方法的唯一要求是为所有资源统一定义它们(即,中介程序不必了解资源类型即可理解请求的含义)。

在以上示例中,POST是统一设计的。它将使狗成为“ bark”。这是不安全的(意味着树皮会对资源产生影响),也不是幂等的(每个请求都产生一个new bark),这很适合POST动词。

程序员会知道:一个POSTbarks收益率bark。响应状态代码(必要时还包含实体主体和标头)用于解释更改内容以及客户端可以并且应该如何进行。

注意:使用的主要来源是: Restful Web Services ”书,HTTP RFCRoy Fielding的博客




编辑:

自问世以来,问题及其答案已经发生了很大变化。在原来的问题询问了URI喜欢的设计:

ACTION http://api.animals.com/v1/dogs/1/?action=bark

以下是为什么它不是一个好选择的解释:

客户端如何告诉服务器应该做什么的数据是方法的信息

  • RESTful Web服务以HTTP方法传达方法信息。
  • 典型的RPC样式和SOAP服务将它们保留在实体正文和HTTP标头中。

哪一部分[客户端希望服务器]要操作的数据的是范围信息

  • RESTful服务使用URI。SOAP / RPC样式服务再次使用实体主体和HTTP标头。

以Google的URI为例 http://www.google.com/search?q=DOG。在那里,方法信息为GET,作用域信息为/search?q=DOG

长话短说:

  • RESTful架构中,方法信息进入HTTP方法。
  • 面向资源的体系结构中,作用域信息进入URI。

经验法则:

如果HTTP方法与方法信息不匹配,则该服务不是RESTful的。如果作用域信息不在URI中,则该服务不是面向资源的。

您可以将“树皮” “操作”放入URL(或实体正文)中并使用POST。没问题,它可以工作,并且可能是最简单的方法,但这不是RESTful

为了使您的服务真正保持RESTful,您可能必须退后一步,然后考虑在此真正要做的事情(它将对资源产生什么影响)。

我无法谈谈您的特定业务需求,但让我举个例子:考虑一个RESTful订购服务,其中的订单位于URI之类 example.com/order/123

现在说我们要取消订单,我们该怎么做?有人可能会认为这是“取消” “行动”,并将其设计为POST example.com/order/123?do=cancel

如上所述,这不是RESTful的。相反,我们可能会用发送到的元素来PUT表示的新表示形式:ordercanceledtrue

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>

就是这样。如果无法取消订单,则可以返回特定的状态代码。(为简单起见,也可以使用POST /order/123/canceled与实体主体类似的子资源设计true。)

在您的特定情况下,您可以尝试类似的操作。这样,例如,当狗吠叫时,GETat /v1/dogs/1/可以包含该信息(例如<barking>true</barking>。或者...如果太复杂了,请放松您的RESTful要求并坚持使用POST

更新:

我不想使答案太大,但是要花一点时间才能掌握将算法(一个动作)作为一组资源公开的倾向。与其根据动作(“在地图上搜索地点”)思考,不如根据动作的结果(“与搜索条件匹配的地图上的地点列表)思考。

如果发现您的设计不适合HTTP的统一接口,您可能会回到这一步。

查询变量 作用域信息,但千万不能分别表示新的资源(/post?lang=en显然是相同的资源作为/post?lang=jp,只是不同的表示)。相反,它们被用于传达客户端状态(如?page=10,使得状态不保留在服务器; ?lang=en也是示例在这里)或输入参数算法资源/search?q=dogs/dogs?code=1)。同样,不是不同的资源。

HTTP动词的(方法)属性:

?action=somethingURI 中显示的另一个清晰点不是RESTful,是HTTP动词的属性:

  • GET并且HEAD是安全的(和幂等的);
  • PUT并且DELETE仅是幂等的;
  • POST 两者都不是。

安全性GETHEAD请求是读取某些数据的请求,而不是更改任何服务器状态的请求。客户端可以发出GETHEAD请求10次,这与发出一次或完全不发出相同。

幂等:一次执行一次或多次都具有相同效果的幂等运算(在数学上,乘以零就是幂等)。如果您DELETE一次使用资源,则再次删除将具有相同的效果(该资源GONE已存在)。

POST是安全的也不是幂等的。POST对“工厂”资源进行两个相同的请求可能会导致两个从属资源包含相同的信息。重载(URI或实体正文中的方法)时POST,所有投注均关闭。

这两个属性对于HTTP协议(通过不可靠的网络!)的成功非常重要:您更新了多少次(GET)页面而没有等待页面完全加载?

创建动作并将其放置在URL中显然会破坏HTTP方法的约定。再一次,该技术允许您执行此操作,但这不是RESTful设计。


我认为,在服务器上调用动作(在URL中指定为动作)不是RESTful的想法。 POST设计用于“向数据处理过程提供数据块”。似乎很多人将资源与行动区分开,但实际上,行动只是一种资源。
雅各布·史蒂文斯

1
@JacobStevens OP稍微改变了问题,所以我必须更新我的答案以使其更直接(检查原始问题,也许您会明白我的意思)。我确实同意POST“为数据处理过程提供一个数据块...”,但不同之处在于,实际上是一个数据块,而不是一个数据块以及要执行的过程(动作,方法,命令)。然后执行。那就是POST重载,而POST重载是RPC样式的设计,而不是RESTful。
acdcjunior 2013年

我假设操作/方法逻辑将存储在服务器上,否则调用的目的是什么?我同意,在您描述的情况下,这不是一个好的设计。但是执行操作的方法或子例程将由URI指定(这是为什么在URL末尾指定为动词的操作资源有用且具有RESTful的另一个原因,尽管许多人对此表示反对。
雅各布·史蒂文斯

6
我们的答案已更新。这有点长,因为似乎需要进行彻底的解释(“请记住,我对REST有基本的了解。”)。使它尽可能清晰完整是一种努力。希望它在某种程度上有用。
acdcjunior 2013年

2
很好的解释,我投了赞成票,但不应在202 Accepted响应中使用Location标头。许多人对RFC的解释似乎是错误的。勾选此stackoverflow.com/questions/26199228/...
Delmo

6

早些回答,但是这个答案与我以前的答案相矛盾,并且遵循了截然不同的策略来寻求解决方案。它显示了如何通过定义REST和HTTP的概念构建HTTP请求。它还使用PATCH代替POSTPUT

它经历了REST约束,然后是HTTP的组件,然后是可能的解决方案。

休息

REST是一组旨在应用于分布式超媒体系统以使其具有可伸缩性的约束。即使是在远程控制动作的上下文中理解它,您也必须考虑将动作远程控制为分布式超媒体系统的一部分-分布式超媒体系统的一部分,该系统用于发现,查看和修改互连的信息。如果麻烦多于其应有的价值,那么尝试使其成为RESTful可能没有好处。如果您只想在客户端上使用“控制面板”类型的GUI来通过端口80触发服务器上的操作,那么您可能想要一个简单的RPC接口,例如通过HTTP请求/响应或WebSocket的JSON-RPC。

但是REST是一种有趣的思维方式,问题中的示例恰好很容易使用RESTful接口进行建模,因此让我们面对挑战,既有趣又教育。

REST 由四个接口约束定义

查明资源;通过表述操纵资源;自我描述的信息;并且,超媒体作为应用程序状态的引擎。

您问如何定义接口,以满足这些限制,一台计算机通过该接口告诉另一台计算机制作狗吠声。具体来说,您希望您的接口是HTTP,并且不想放弃按预期使用时使HTTP RESTful的功能。

让我们从第一个约束开始:资源标识

可以命名的任何信息都可以是资源:文档或图像,临时服务(例如“洛杉矶今天的天气”),其他资源的集合,非虚拟对象(例如人)等。

因此,狗是一种资源。它需要被识别。

更准确地,资源R是随时间变化的隶属函数M Rt),其在时间t上映射到等效的一组实体或值。集合中的值可以是资源表示和/或资源标识符

您可以通过获取一组标识符和表示并说它们在给定时间相互关联来对狗进行建模。现在,让我们使用标识符“ dog#1”。这将我们带到第二和第三个约束:资源表示自我描述

REST组件通过使用表示来捕获资源的当前或预期状态并在组件之间传输该表示,从而对资源执行操作。表示形式是字节序列,外加描述这些字节的表示形式元数据。

以下是捕获狗的预期状态的字节序列,即我们希望与标识符“狗#1”相关联的表示形式(请注意,由于它不考虑狗的名字,健康状况,因此只代表状态的一部分,甚至是过去的树皮):

自从执行此状态更改以来,它每10分钟发出一次吠叫声,并且将无限期地继续。

它应该附加到描述它的元数据上。此元数据可能有用:

这是英文声明。它描述了部分预期状态。如果收到多次,则只允许第一个生效。

最后,让我们看一下第四个约束:HATEOAS

REST ...将应用程序视为信息的内聚结构,并控制用户可通过其执行所需任务的替代方案。例如,在在线词典中查找单词是一个应用程序,在虚拟博物馆中浏览或浏览一组课堂笔记以进行考试也是如此。...应用程序的下一个控制状态驻留在第一个请求资源的表示中,因此获得第一个表示是优先级。因此,模型应用程序是一种引擎,它通过检查当前表示形式中的替代状态转换并从中进行选择,从而从一种状态移至另一种状态。

在RESTful接口中,客户端接收资源表示,以便弄清楚应该如何接收或发送表示。在应用程序的某处必须有一个表示形式,客户端可以从中弄清楚如何接收或发送它应该能够接收或发送的所有表示形式,即使它遵循一系列表示形式来获得该信息。这似乎很简单:

客户要求提供标识为主页的资源的表示形式;作为响应,它得到一个表示,其中包含客户端可能想要的每条狗的标识符。客户端从中提取一个标识符,并询问服务它如何与所识别的狗进行交互,该服务表示客户端可以发送描述该狗预期状态的一部分的英语语句。然后,客户端发送这样的语句并接收成功消息或错误消息。

HTTP

HTTP实现了REST约束,如下所示:

资源标识:URI

资源表示:实体

自我描述:方法或状态代码,标头以及实体主体的可能部分(例如XML模式的URI)

帽子:超链接

你已经决定 http://api.animals.com/v1/dogs/1使用URI。假设客户从站点的某个页面上获得了此信息。

让我们使用这个实体主体(的值next是一个时间戳;值的0意思是“在收到此请求时”):

{"barks": {"next": 0, "frequency": 10}}

现在我们需要一种方法。PATCH符合我们决定的“预期状态的一部分”描述:

PATCH方法请求将请求实体中描述的一组更改应用于由Request-URI标识的资源。

和一些标题:

指示实体的语言: Content-Type: application/json

为了确保它只发生一次: If-Unmodified-Since: <date/time this was first sent>

我们有一个要求:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}

成功后,客户应收到204状态码作为回应,205如果/v1/dogs/1/已更改以反映新的吠叫时间表。

失败时,应该收到一个 403和一条有用的消息,说明为什么。

对于REST来说,服务不必以表示形式来响应即可反映树皮计划GET /v1/dogs/1/,但如果JSON表示形式包括以下内容,则是最有意义的:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}

将cron作业视为服务器从界面隐藏的实现细节。这就是通用接口的优点。客户端不必知道服务器在后台做什么。它关心的只是该服务理解并响应请求的状态更改。


3

大多数人使用POST为此。适用于“在没有其他HTTP方法合适的情况下执行任何不安全或非幂等的操作”。

诸如XMLRPC之类的 API 使用POST来触发可以运行任意代码的动作。“操作”包含在POST数据中:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
         </param>
      </params>
   </methodCall>

给出了RPC示例,以表明POST是服务器端方法的HTTP动词的常规选择。这是Roy Fielding关于POST的想法 -他几乎说使用指定的HTTP方法是RESTful的。

请注意,RPC本身不是非常RESTful的,因为它不是面向资源的。但是,如果您需要无状态,缓存或分层,则进行适当的转换并不难。有关示例,请参见http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/


我认为您可以URLencode参数而不将其放在查询字符串中
tacos_tacos_tacos 2013年

@Kirk是的,但稍作修改后,请删除最后的正斜杠: POST api.animals.com/v1/dogs1?action=bark
Raymond Hettinger 2013年

如果您遵循此答案中的建议,请记住,生成的API将不是RESTful的。
Nicholas Shanks 2013年

2
这不是RESTful的,因为 HTTP将URL建立为资源的标识符,而URL 则不/RPC2标识资源-它标识服务器技术。取而代之的是,这用来methodName尝试“识别”“资源”-但是即使那样,它也不能从名词/动词的区别中受益。这里唯一的类似于“动词”的东西是methodCall。这就像“ do state-name-retrieval”而不是“ retrieve state-name”一样,后者意义更大。
约旦

+1的链接;非常有用的信息,“有规则的RPC”实验是有创造力的。
约旦

2

POSTHTTP方法设计

向数据处理过程提供数据块

Roy Fielding使用REST 来处理非CRUD映射的操作的服务器端方法,因此您在这方面做得很好,这就是为什么POST使其成为非幂等的原因。POST将处理大多数数据发布到服务器端方法以处理信息的问题。

就是说,在您的咆哮场景中,如果您希望每10分钟执行一次服务器端吠叫,但由于某种原因需要触发器源自客户端PUT,则由于它的幂等性,因此可以更好地达到目的。好吧,严格来说,在这种情况下,不会有多个POST请求导致狗发出喵叫的明显风险,但是无论如何,这就是两种类似方法的目的。我对类似的SO问题的回答可能对您有用。


1
PUT与POST都是关于URL的。在9.6 PUT之后的第三段中,这两种方法的目的是使PUTURL指向应由客户端内容替换的内容,而POSTURL则指应根据需要处理客户端内容的内容。
约旦

1

如果我们假设“吠叫”是消费者可以采取行动的内部/依赖/子资源,那么我们可以说:

POST http://api.animals.com/v1/dogs/1/bark

1号狗吠

GET http://api.animals.com/v1/dogs/1/bark

返回最后一个树皮时间戳

DELETE http://api.animals.com/v1/dogs/1/bark

不适用!所以忽略它。


如果你认为这只是基于REST /v1/dogs/1/bark是一种资源本身,并POST成为其中的资源的内部状态,应如何改变的描述。我发现,仅将其视为/v1/dogs/1/一种资源并在实体中指出它应该吠叫是更有意义的。
约旦

嗯,这是您可以更改其状态的资源。由于更改其状态的结果是发出噪音,因此请不要减少资源!您正在将Bark视为动词,这就是为什么您不能将其视为一种资源。我将其视为可以更改其状态的依赖资源,并且由于其状态为布尔值,因此我看不出有任何理由在实体主体中提及它。那只是我的意见。
bolbol

1

某些答案的早期版本建议您使用RPC。你并不需要看RPC,因为它完全可以做你想做的,同时秉承了REST的约束。

首先,不要在URL中放置操作参数。URL定义将操作应用到的内容,查询参数是URL的一部分。应该将其完全视为名词。 http://api.animals.com/v1/dogs/1/?action=bark是与的不同资源-不同的名词http://api.animals.com/v1/dogs/1/。[NB阿斯卡已经移除了?action=bark从问题URI。例如,比较http://api.animals.com/v1/dogs/?id=1http://api.animals.com/v1/dogs/?id=2。不同的资源,仅通过查询字符串来区分。因此,必须在请求正文中定义请求的操作,除非它直接与无主体的现有方法类型(TRACE,OPTIONS,HEAD,GET,DELETE等)相对应。

接下来,确定该动作是否为“ 幂等 ”,这意味着可以重复执行该动作而不会产生不利影响(更多解释请参见下一段)。例如,如果客户端不确定发生了预期的效果,则可以重复将值设置为true。他们再次发送请求,该值保持为true。将1加到数字上不是幂等的。如果客户端发送Add1命令,不确定它是否有效,然后再次发送,服务器是否添加了一两个?一旦你已经确定的是,你在一个更好的位置之间进行选择PUT,并POST为您的方法。

幂等意味着可以重复请求而不会改变结果。这些影响不包括日志记录和其他此类服务器管理员活动。使用您的第一个和第二个示例,向同一个人发送两封电子邮件与发送一封电子邮件确实会导致状态不同(收件人在收件箱中有两封电子邮件,他们可能会将其视为垃圾邮件),因此我肯定会使用POST 。如果示例2中的barkCount旨在由您的API用户看到或影响客户端可见的内容,那么它也会使请求成为非幂等。如果仅由您查看,则它将视为服务器日志记录,在确定幂等性时应将其忽略。

最后,确定您希望执行的操作是否可以立即成功执行。BarkDog是一项快速完成的动作。RunMarathon不是。如果您的操作很慢,请考虑202 Accepted在响应正文中返回一个带有URL的URL,供用户轮询以查看该操作是否完成。或者,让用户POST到类似的列表URL /marathons-in-progress/,然后在完成操作后将其从进行中的ID URL重定向到/marathons-complete/ URL。
对于#1和#2的特定情况,我将让服务器托管一个队列,然后客户端将一批地址发送到该队列。该操作将不是SendEmails,而是类似AddToDispatchQueue的操作。然后,服务器可以轮询队列以查看是否有任何电子邮件地址在等待,并在找到电子邮件地址时发送电子邮件。然后,它更新队列以指示已执行挂起的操作。您将有另一个URI,向客户端显示客户端的当前队列状态。为了避免重复发送电子邮件,服务器还可以保留向其发送此电子邮件的收件人的日志,并对照该地址检查每个地址,以确保它永远不会将两个电子邮件发送到相同的地址,即使您两次将相同的列表过帐到该地址也是如此。队列。

在为任何内容选择URI时,请尝试将其视为结果而不是操作。例如google.com/search?q=dogs显示结果的单词“狗”的搜索。它不必执行搜索。

您列表中的案例3和案例4也不是幂等的操作。您建议建议的不同效果可能会影响API设计。在所有四种情况下,我都会使用相同的API,因为所有四种都会更改“世界状态”。


假设操作是搅动庞大的电子邮件队列,然后将消息发送给一群人。那是幂等的吗?PUT或POST是幂等动作吗?
Kirk Ouimet 2013年

@kirk我扩大了答案。
Nicholas Shanks

0

请参阅我的新答案 -它与此答案相矛盾,并更清晰,更准确地解释了REST和HTTP。

这是一个建议,它可能是RESTful的,但肯定不是唯一的选择。要在服务收到请求时开始吠叫:

POST /v1/dogs/1/bark-schedule HTTP/1.1
...
{"token": 12345, "next": 0, "frequency": 10}

token 是一个任意数字,无论发送多少次此请求,它都可以防止多余的树皮。

next指示下一个树皮的时间;的值0意味着“尽快”。

无论何时GET /v1/dogs/1/bark-schedule,您都应该得到类似的信息,其中t是最后一个树皮的时间,ut + 10分钟:

{"last": t, "next": u}

我强烈建议您使用相同的网址来请求用于查询狗当前吠叫状态的吠叫。对于REST来说不是必需的,但它强调修改计划的行为。

适当的状态码可能是205。我正在想象一个客户端,它查看当前日程表,POST将其更改为相同的URL来更改它,并由服务指示该日程表重新显示该日程表以证明它已被更改。

说明

休息

暂时不要使用HTTP。必须理解,资源是一个需要时间作为输入并返回包含标识符表示形式的集合的函数。让我们简化为:资源是标识符和表示的集合RR可以更改-可以添加,删除或修改成员。(虽然它的坏的,不稳定的设计,以删除或修改标识符。)我们说的标识符是一个元件ř识别- [R ,并且表示是一个元件- [R代表- [R

假设R是狗。您碰巧将R标识为/v1/dogs/1。(含义/v1/dogs/1R的成员。)这只是您可以识别R的许多方式之一。您还可以将R标识为/v1/dogs/1/x-rays/v1/rufus

您如何表示R?也许有照片。也许会带有一组X射线。或者可能带有指示R最后一次发出吠叫的日期和时间。但是请记住,这些都是相同资源的表示。/v1/dogs/1/x-rays是同一资源的标识符,该标识符由对问题“ R何时最后发出树皮?” 的答案表示

HTTP

如果您无法引用所需的资源,则资源的多种表示形式不是很有用。这就是HTTP有用的原因:它使您可以将标识符连接到制图表达。也就是说,这是服务接收URL并确定将哪种表示形式提供给客户端的一种方法。

至少是GET这样。PUT基本上是逆GET:你PUT的表示- [R的网址,如果你想为将来GET请求该URL返回[R ,与一些可能的翻译像JSON到HTML。

POST是一种修改表示形式的宽松方法。考虑一下存在彼此对应的显示逻辑和修改逻辑,它们都对应于同一URL。POST请求是对修改逻辑的请求,以便处理该信息并在服务认为合适时修改任何表示形式(不仅是位于同一URL的表示形式)。请注意9.6 PUT之后的第三段:您不会用新内容替换URL中的内容;您要在URL上请求事物处理某些信息并以信息表示形式进行智能响应。

在我们的案例中,我们要求位于的修改逻辑/v1/dogs/1/bark-schedule(与显示逻辑的对等物,它告诉我们上一次吠叫的时间以及下一次吠叫的时间)来处理我们的信息并相应地修改某些表示形式。响应将来GET,对应于相同URL的显示逻辑将告诉我们,狗现在正按我们的意愿吠叫。

将cron作业视为实现细节。HTTP处理查看和修改表示形式。从现在开始,该服务将告知客户狗何时最后一次吠叫,何时下一次吠叫。从服务的角度来看,这是诚实的,因为这些时间与过去和计划的cron工作相对应。


-1

REST是一种面向资源的标准,它不是像RPC那样由动作驱动的。

如果您希望服务器吠叫,则应研究JSON-RPC之类的不同想法,或者研究websockets通信。

在我看来,每次尝试使其保持RESTful都会失败:您可以POST使用action参数发出a ,您没有创建任何新资源,但是由于可能会有副作用,因此您会更安全。


POST设计用于“向数据处理过程提供数据块”。似乎很多人将资源与行动区分开,但实际上,行动只是一种资源。在服务器上调用操作资源仍然是一个统一的界面,可缓存,模块化和可扩展。它也是无状态的,但是如果客户端被设计为期望响应,则可能会被违反。但是Roy Fielding在REST上打算在服务器上调用“ void方法” 。
雅各布·史蒂文斯

正如我在回答中所描述的那样,您可以在REST中通过要求服务器从现在开始说“您的操作已完成”来隐式地使服务器执行操作,而RPC基于仅要求服务器执行操作的思想那个行动。两者都非常有意义,就像命令式和声明式编程都有意义一样。
约旦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.