【问题标题】:Call a Server-side Method on a Resource in a RESTful Way以 RESTful 方式调用资源上的服务器端方法
【发布时间】:2013-05-28 11:54:05
【问题描述】:

请记住,我对 REST 有初步的了解。假设我有这个网址:

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

现在,我想让服务器让狗吠。只有服务器知道如何做到这一点。假设我想让它在 CRON 工作上运行,让狗在永恒的剩余时间里每 10 分钟吠一次。那个电话是什么样子的?我有点想这样做:

网址请求:

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

在请求正文中:

{"action":"bark"}

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

编辑澄清

关于“吠叫”方法的作用的更多说明。以下是一些可能导致不同结构的 API 调用的选项:

  1. bark 只是向 dog.email 发送了一封电子邮件,没有任何记录。
  2. bark 向 dog.email 发送电子邮件,并将 dog.barkCount 增加 1。
  3. bark 创建一个新的“bark”记录,其中包含 bark.timestamp 记录当吠声发生时。它还将 dog.barkCount 增加 1。
  4. bark 运行系统命令从 Github 拉取最新版本的狗代码。然后它会向 dog.owner 发送一条短信,告诉他们新的狗代码正在生产中。

【问题讨论】:

  • 有趣的是,添加赏金似乎吸引了比原来更糟糕的答案;-) 在评估答案时请记住:1) HTTP 动词的规范排除了除 POST 之外的任何选择。 2) REST 与 URL 结构无关——它是一个通用的约束列表(无状态、可缓存、分层、统一接口等),而不是带来好处(可扩展性、可靠性、可见性等)。 3) 当前实践(例如在 RPC 规范中使用 POST)胜过正在制定自己的 API 规则的定义主义者。 4) REST 需要一个统一的接口(遵循 HTTP 规范)。
  • @Kirk 您对新答案有何看法?有什么你仍然想知道但没有在其中任何一个中解决的吗?如果可以提供更多帮助,我将非常乐意再次编辑我的答案。
  • @RaymondHettinger PATCH 可以是合适的。我在我的 answer 末尾解释了原因。
  • PATCH 仅适用于将 dog.barkCount 加一。 POST 是发送电子邮件、创建新的吠叫记录、运行从 Github 下载的命令或触发文本消息的方法。 @Jordan,您对 PATCH RFC 的阅读是富有想象力的,但与它作为 PUT 的变体用于部分资源修改的意图有些不一致。我不认为你通过提出非常规的 HTTP 规范读数来帮助 OP,而不是承认使用 POST 进行远程过程调用的标准做法。
  • @RaymondHettinger 谁的实践事实上标准化了 POST?我见过的所有标准 RPC 接口都通过实体(不是 RESTful)与 URI 来识别资源,因此优先考虑 RPC 约定的有效答案无论如何都需要是非常规的,我认为这反驳了传统 RPC 的价值:一个是富有想象力的或不一致的. POST 永远不会出现错误,因为它是数据处理的全部,但还有更具体的方法。 REST 意味着命名资源并描述其状态的变化,而不是命名状态变化的过程。 PATCH 和 POST 都描述了状态变化。

标签: api rest url api-design restful-architecture


【解决方案1】:

为什么要以 RESTful 设计为目标?

RESTful 原则为 Web 服务 API 设计带来了使网站易于使用的功能(供随机人类用户“冲浪”它们) /strong>,所以它们很容易被程序员使用。 REST isn't good because it's REST, it's good because it's good. 它之所以好主要是因为它简单

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

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

同样,Java/C# 代码不是面向对象的,仅使用 HTTP 并不能使设计成为 RESTful。人们可能会忙于思考他们的服务应该调用的操作和远程方法。难怪这将主要以 RPC 风格的服务(或 REST-RPC 混合)结束。第一步是换个角度思考。可以通过多种方式实现 RESTful 设计,其中一种方式是从资源而非操作的角度考虑您的应用程序:

? 它不会考虑它可以执行的操作(“在地图上搜索地点”)...

...尝试考虑这些操作的结果(“地图上与搜索条件匹配的地点列表”)。

我将在下面举例。 (REST的另一个关键方面是HATEOAS的使用——这里就不刷了,不过我快说at another post。)


第一个设计的问题

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

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"} 不是文档,它实际上是方法你试图ninja-sneak进入服务;和
  • /v1/dogs/1/ URI 表示“狗”资源(可能是带有id==1 的狗),而不是文档处理器。

所以我们现在所知道的是,上面的设计并不是那么 RESTful,但那到底是什么? 它有什么不好?基本上,它是不好的,因为它是复杂的 URI,具有复杂的含义。你无法从中推断出任何东西。程序员怎么会知道狗有一个bark 可以秘密注入POST 的动作?


设计问题的 API 调用

所以让我们切入正题,尝试通过考虑资源方面来以 REST 方式设计这些树皮。请允许我引用Restful Web Services这本书:

POST 请求是尝试从现有资源创建新资源 一。现有资源可能是新资源的父级 数据结构意义上,树的根是所有树的父级 它的叶子节点。或者现有资源可能是一个特殊的“工厂” 唯一目的是生成其他资源的资源。这 与POST 请求一起发送的表示描述了初始 新资源的状态。与 PUT 一样,POST 请求不需要 完全包含一个表示。

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

根据我们已经得到的推理:

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

现在列表中的每个案例都有特定的行为。

##1。 bark 只是向dog.email 发送了一封电子邮件,没有任何记录。

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


1.1 bark 向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 bark 向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 都是可追踪的。然后客户端可以向bark URI 发出GET 以了解其当前状态。甚至可以使用DELETE 取消它。


2。 bark 向dog.email 发送电子邮件,然后将dog.barkCount 增加1

如果您想让客户知道 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 about 303

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

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


3。当吠声发生时,bark 使用bark.timestamp 记录创建一个新的“bark”记录。它还将 dog.barkCount 增加 1。

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 (as the HTTP RFC says)。

保存的时间戳是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 以了解当前状态(如果代码被提取,则电子邮件已发送给所有者等)。每当狗改变时,303 都适用。


总结

引用Roy Fielding:

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

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

程序员会知道:POSTbarks 会产生 bark。响应状态代码(必要时还带有实体主体和标头)用于解释发生了什么变化以及客户端可以和应该如何继续。

注意:使用的主要来源是:“Restful Web Services”书、HTTP RFCRoy Fielding's blog




编辑:

自首次创建以来,问题和答案已经发生了很大变化。 原始问题询问了 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 中,则服务不是面向资源的。

您可以将 "bark" "action" 放在 URL(或实体正文中)并使用 POST。没问题,它可以工作,并且可能是最简单的方法,但这不是 RESTful

为了让您的服务真正保持 RESTful,您可能需要退后一步,想想您在这里真正想要做什么(它会对资源产生什么影响)。

我无法谈论您的具体业务需求,但让我举个例子:考虑一个 RESTful 订购服务,其中订单的 URI 为 example.com/order/123

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

正如我们上面所说的那样,这不是 RESTful。相反,我们可以PUTorder 提供一个新的表示,并将canceled 元素发送到true

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

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

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

在您的特定情况下,您可以尝试类似的方法。这样,例如,当狗在吠叫时,/v1/dogs/1/ 上的 GET 可以包含该信息(例如 &lt;barking&gt;true&lt;/barking&gt;。或者......如果这太复杂了,放宽你的 RESTful 要求并坚持使用POST

更新:

我不想让答案太大,但是需要一段时间才能掌握将算法(动作)公开为一组资源的窍门。与其从行动的角度思考(“在地图上搜索地点”),不如从行动的结果(“地图上的地点列表匹配搜索的地图 标准”)。

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

查询变量 范围信息,但表示新资源(/post?lang=en 显然相同 资源为/post?lang=jp,只是一种不同的表示形式)。相反,它们用于传达客户端状态(如?page=10,因此该状态不会保存在服务器中;?lang=en 也是此处的示例)或输入参数 算法资源 (/search?q=dogs, /dogs?code=1)。同样,不是不同的资源。

HTTP 动词的(方法)属性:

另一个清楚的点是在 URI 中显示 ?action=something 不是 RESTful,是 HTTP 动词的属性:

  • GETHEAD 是安全的(并且是幂等的);
  • PUTDELETE 仅是幂等的;
  • POST 两者都不是。

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

幂等性:一种幂等运算,无论您应用一次还是多次都具有相同的效果(在数学中,乘以零是幂等的)。如果你DELETE一个资源一次,再次删除也会有同样的效果(资源已经是GONE了)。

POST 既不安全也不幂等。向“工厂”资源发出两个相同的POST 请求可能会导致两个从属资源包含相同的 信息。重载(URI 或实体主体中的方法)POST,所有赌注都关闭了。

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

创建一个 action 并将其放置在 URL 中显然违反了 HTTP 方法的约定。再一次,技术允许你,你可以做到,但这不是 RESTful 设计。

【讨论】:

  • @JacobStevens OP 稍微改变了问题,所以我必须更新我的答案以使其更直接(检查original question,也许你会明白我的意思)。我同意POST“向数据处理进程提供数据块...”,但不同之处在于,一个 data 块,而不是一个数据块和之后要执行的过程(动作、方法、命令)。那就是POST重载,POST重载是RPC-Style设计,不是RESTful。
  • 我们更新了答案。它有点长,因为似乎需要一个彻底的解释(“请记住,我对 REST 有一个基本的了解。”)。使它尽可能清晰完整是一种斗争。希望它在某些方面有用。
  • 很好的解释,我投票了,但 Location 标头不应该用于 202 Accepted 响应。这似乎是许多人对 RFC 所做的错误解释。检查这个stackoverflow.com/questions/26199228/…
  • 这是一个很好的答案,对我帮助很大。我想知道一件事:有时您在 URI 片段中使用/bark/,有时使用/barks/。例如。您在 /v1/dogs/1/barks 上的 POST 但您在响应的 Location 标头中有 .../dogs/1/bark/a65h44。资源应该总是复数,对吧?
  • @maximedupre 之类的。我会将“它只提供一个含义”改写为“它只是标准的一部分”。 As Roy statedLocation 可以与202 一起使用,只是它没有此状态代码的标准行为,因此您必须确保使用其他方式可以理解响应,例如包含超链接的超文本。换句话说:标准并没有说明Location对于202是什么意思,但它并不禁止你用它来表示202。如果你使用它,你必须向用户解释它是什么意思。我试图在答案中指出这一点......
【解决方案2】:

answered earlier,但这个答案与我的旧答案相矛盾,并遵循了一种截然不同的解决方案。它显示了 HTTP 请求是如何从定义 REST 和 HTTP 的概念构建的.它还使用PATCH 而不是POSTPUT

它通过 REST 约束,然后是 HTTP 的组件,然后是一个可能的解决方案。

休息

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

但是 REST 是一种令人着迷的思维方式,并且问题中的示例恰好很容易使用 RESTful 接口进行建模,所以让我们为了乐趣和教育而接受挑战。

REST 通过四个接口约束为defined

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

你问如何定义一个接口,满足这些约束,一台计算机通过它告诉另一台计算机让狗叫。具体来说,您希望您的接口是 HTTP,并且您不想放弃在按预期使用时使 HTTP 成为 RESTful 的功能。

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

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

所以狗是一种资源。它需要被识别。

更准确地说,资源R是一个随时间变化的隶属函数MR(t),它对于时间 t 映射到一组等效的实体或值。集合中的值可以是资源表示和/或资源标识符

为狗建模,方法是采用一组标识符和表示,并说它们在给定时间都相互关联。现在,让我们使用标识符“dog #1”。这给我们带来了第二个和第三个约束:资源表示自我描述

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

以下是捕获狗的预期状态的字节序列,即我们希望与标识符“狗#1”相关联的表示(注意它仅代表状态的一部分,因为它不考虑狗的状态)姓名、健康状况,甚至过去的吠叫):

自此状态更改生效以来,它每 10 分钟吠一次,并将无限期地持续下去。

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

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

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

REST ... 将应用程序视为信息和控制选项的内聚结构,用户可以通过它执行所需的任务。例如,在在线词典中查找一个单词就是一个应用程序,就像参观虚拟博物馆或复习一组课堂笔记以备考一样。 ...应用程序的下一个控制状态驻留在第一个请求资源的表示中,因此获得第一个表示是优先级。 ...因此,模型应用程序是一个引擎,它通过检查并从当前表示集中的替代状态转换中进行选择,从一个状态移动到下一个状态。

在 RESTful 接口中,客户端接收资源表示,以便确定它应该如何接收或发送表示。在应用程序的某个地方必须有一个表示,客户端可以从中找出如何接收或发送它应该能够接收或发送的所有表示,即使它遵循一个表示链来获得该信息。这看起来很简单:

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

HTTP

HTTP 实现 REST 约束如下:

资源标识:URI

资源表示:实体主体

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

HATEOAS:超链接

您已决定将 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: &lt;date/time this was first sent&gt;

我们有一个请求:

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 状态代码作为响应,如果/v1/dogs/1/ 的表示已更改以反映新的吠叫时间表,则应收到205

如果失败,它应该会收到一个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 thoughts on POST——他几乎说使用指定的 HTTP 方法是 RESTful。

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

    【讨论】:

    • 我想你会 URLencode 参数而不是把它放在查询字符串中
    • @Kirk 是的,但稍作修改,删除最后的正斜杠:POST api.animals.com/v1/dogs1?action=bark
    • 如果您遵循此答案中的建议,请记住生成的 API 不会是 RESTful。
    • 这不是 RESTful 因为 HTTP 将 URL 建立为资源的标识符,而 /RPC2 的 URL 没有任何作用来识别资源——它识别服务器技术。相反,它使用methodName 来尝试“识别”“资源”——但即便如此,它也不能从名词/动词的区别中受益;这里唯一类似“动词”的东西是methodCall。这就像“do state-name-retrieval”而不是“retrieve state-name”——后者更有意义。
    • 链接+1;信息量很大,“自以为是的 RPC”实验很有创意。
    【解决方案4】:

    POSTHTTP method designed

    向数据处理进程提供数据块...

    处理非 CRUD 映射操作的服务器端方法是 Roy Fielding intended 与 REST,所以你很好,这就是为什么 POST 是非幂等的。 POST 将处理大多数将数据发布到服务器端方法以处理信息。

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

    【讨论】:

    • PUT 与 POST 都是关于 URL 的。 9.6 PUT 之后的第三段说这两种方法的目的是,PUT URL 指的是应该由客户端内容替换的内容,POST URL 指的是应该处理客户想要的内容。
    【解决方案5】:

    如果我们假设 Barking 是消费者可以操作的内部/依赖/子资源,那么我们可以说:

    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
    

    不适用!所以忽略它。

    【讨论】:

    • 仅当您将/v1/dogs/1/bark 视为资源本身 并且POST 是对该资源的内部状态应如何更改的描述时,这才是RESTful。我发现将/v1/dogs/1/ 视为一种资源并在实体主体中指出它应该吠叫更有意义。
    • mmm.. 嗯,它是一个资源,你可以改变它的状态。因为改变它的状态的结果是制造噪音,并没有减少它的资源!您正在将 Bark 视为动词(即),这就是为什么您不能将其视为资源的原因。我将其视为可以更改其状态的依赖资源,并且由于其状态是布尔值,因此我认为没有任何理由在实体主体中提及它。这只是我的看法。
    【解决方案6】:

    一些答案​​的早期版本建议您使用 RPC。您无需依赖 RPC,因为它完全有可能在遵守 REST 约束的同时做您想做的事情。

    首先,不要在 URL 中放置操作参数。 URL 定义了您正在应用操作的什么,查询参数是 URL 的一部分。它应该完全被认为是一个名词。 http://api.animals.com/v1/dogs/1/?action=barkhttp://api.animals.com/v1/dogs/1/ 是不同的资源——不同的名词。 [注Asker 已从问题中删除了 ?action=bark URI。] 例如,将 http://api.animals.com/v1/dogs/?id=1http://api.animals.com/v1/dogs/?id=2 进行比较。不同的资源,仅通过查询字符串区分。因此,除非它直接对应于无主体的现有方法类型(TRACE、OPTIONS、HEAD、GET、DELETE 等),否则您的请求的操作必须在请求主体中定义。

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

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

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

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

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

    【讨论】:

    • 假设操作是搅动一个巨大的电子邮件队列并向一群人发送消息。那是幂等的吗? PUT 或 POST 是幂等操作吗?
    • @kirk 我已经扩展了我的答案。
    【解决方案7】:

    见我的new answer——它与这个矛盾,更清晰准确地解释了 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}

    我强烈建议您使用相同的 URL 来请求吠叫,以便了解狗的当前吠叫状态。这对 REST 来说不是必需的,但它强调了修改计划的行为。

    适当的状态码可能是205。我在想象一个客户端查看当前的日程安排,POSTs 到相同的 URL 以更改它,并由服务指示再次查看日程安排以证明它已被更改。

    说明

    休息

    暂时忘记 HTTP。必须了解resource 是一个函数,它需要时间作为输入并返回一个包含标识符表示 的集合。让我们将其简化为:资源是一组 R 标识符和表示; R 可以更改 -- 可以添加、删除或修改成员。 (尽管删除或修改标识符是不好的、不稳定的设计。)我们说作为 R 元素的标识符标识了 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 有用的原因:它让你connect identifiers to representations。也就是说,它是服务接收 URL 并决定向客户端提供哪种表示的一种方式。

    至少,GET 是这样做的。 PUT 基本上是 GET 的倒数:如果您希望将来 GET 请求该 URL 返回 r,则 PUT 在 URL 处的表示>,还有一些可能的翻译,例如 JSON 到 HTML。

    POST 是一种更宽松的修改表示的方式。考虑一下显示逻辑和修改逻辑,它们是彼此对应的——都对应于同一个 URL。 POST 请求是对修改逻辑的请求,以处理信息并修改服务认为合适的任何表示(不仅仅是位于同一 URL 的表示)。注意9.6 PUT之后的第三段:你不是用新内容替换了URL处的东西;您是在请求 URL 处的事物处理一些信息并以信息表示的形式智能地做出响应。

    在我们的例子中,我们要求/v1/dogs/1/bark-schedule 的修改逻辑(它与显示逻辑的对应部分告诉我们它最后一次吠叫什么时候以及下一次吠叫什么时候)来处理我们的信息并相应地修改一些表示。响应未来的GETs,同一个 URL 对应的显示逻辑会告诉我们,狗现在正在如我们所愿地吠叫。

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

    【讨论】:

      【解决方案8】:

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

      如果你想让你的服务器,你应该看看不同的想法,比如JSON-RPC,或者websockets通信。

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

      【讨论】:

      • POST was designed for "providing a block of data...to a data-handling process"。似乎很多人将资源与动作区分开来,但实际上动作只是一种资源。在服务器上调用动作资源仍然是一个统一的接口,可缓存、模块化和可扩展。它也是无状态的,但如果客户端被设计为期望响应,则可能会违反它。但是在服务器上调用“void 方法”就是Roy Fielding intended with REST
      • 正如我在my answer 中描述的那样,您可以在 REST 中通过要求服务器说“您的操作已完成”来隐式地导致服务器执行操作,而 RPC 是基于只是要求服务器执行操作的想法。两者都很有意义,就像命令式和声明式编程都很有意义一样。
      猜你喜欢
      • 2013-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2011-08-23
      相关资源
      最近更新 更多