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

请记住,我对 REST 有一个基本的了解:

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

现在,我想让服务器让狗叫。只有服务器知道如何做到这一点。比方说,我想让它运行在一个 CRON 作业,使狗吠声每10分钟,其余的永恒。那个电话看起来像什么?我有点想这么做:

网址要求:

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

在请求机构中:

{"action":"bark"}

在你因为我创建了自己的 HTTP 方法而生我的气之前,先帮帮我,告诉我应该如何以 RESTful 方式调用服务器端方法。:)

编辑澄清

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

  1. 巴克只是给狗发了封邮件,什么都没记录。
  2. BarkCount 向 dog.email 发送一封电子邮件,增量 dog.barkCount 为1。
  3. 在吠叫发生时,吠叫会创建一个新的“吠叫”记录,它还会将 dog.barkCount 增加1。
  4. 巴克运行一个系统命令从 Github 下载最新版本的狗代码。然后向 dog.owner 发送一条短信,告诉他们新的狗代码正在生产中。
73477 次浏览

大多数人为此目的使用 职位。它适合于执行“任何不安全或非幂等的操作,当没有其他 HTTP 方法似乎是合适的”。

诸如 XMLRPC之类的 API 使用 职位触发可以运行任意代码的操作。“行动”包含在 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 动词的传统选择。这里是 菲尔丁对 POST 的思考——他几乎说使用指定的 HTTP 方法是 RESTful 的。

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

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

如果你想要你的服务器到 ,你应该研究不同的想法,如 JSON-RPC,或进入 websockets 通信。

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

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

RESTful 原则 带来的功能,使网站容易(对于一个 随机的人类使用者来说是“冲浪”它们) 到 Web 服务 API 设计,所以它们对于程序员来说很容易使用。它是好的主要是因为它是 很简单

纯 HTTP 的简单性(没有 SOAP 信封和单 URI 重载的 POST服务) ,也就是 有些人可能会称之为“缺乏特性”,实际上是 它最强大的力量。HTTP 马上要求您使用 可寻址性无国籍状态: 这两个基本的设计决策使 HTTP 可伸缩到当今的大型站点(和大型服务)。

但是 REST 并不是万能的: 有时是 RPC 风格的(“远程过程调用”-例如 SOAP) 可能是合适的,有时其他需求优先于 Web 的优点。没关系。我们不喜欢 是不必要的复杂性的地方。程序员或公司经常为一个普通的旧 HTTP 可以很好地处理的任务引入 RPC 样式的服务。这样做的结果是,HTTP 被简化为一个巨大的 XML 有效负载的传输协议,该协议解释了“真正”发生的事情(而不是 URI 或 HTTP 方法提供的线索)。由此产生的服务过于复杂,不可能进行调试,并且除非您的客户端按照开发人员的意图拥有 一模一样,否则将无法工作。

就像 Java/C # 代码可以是面向 没有对象的一样,仅仅使用 HTTP 并不能使设计变得 RESTful。一个人可能会被他们的服务 在行动和远程方法方面思考赶上,应该被称为。难怪这种情况最终会以 RPC 样式的服务(或 REST-RPC 混合)告终。第一步是换个角度思考。RESTful 设计可以通过多种方式实现,一种方式是 考虑你的应用程序的资源,而不是行动:

而不是考虑它可以执行的行动(“做一个地图上的地方搜索”) ..。

... 试着从 结果的角度来考虑这些行为(“地图上符合搜索条件的地点列表”)。

我将在下面举几个例子。 (REST 的另一个关键方面是 HATEOAS 的使用——我在这里不刷它,但是我很快地讨论了 在另一个岗位。)


第一次设计的问题

让我们来看看这个设计方案:

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

首先,我们不应该考虑创建一个 新的 HTTP 动词(ACTION)。一般来说,这是 不受欢迎的几个原因:

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

现在让我们看看 考虑使用 POST(我将在下面讨论为什么,现在请相信我的话) :

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


{"action":"bark"}

这个 可以可以... 但是 除非:

  • {"action":"bark"}是一个文档;
  • /v1/dogs/1/是一个“文档处理器”(类似于工厂) URI

我不太了解你的系统,但我敢打赌两者都不是真的:

  • {"action":"bark"} 不是文件,它实际上是 就是方法你正在尝试进入 忍者神偷的服务;
  • /v1/dogs/1/ URI 表示“ dog”资源(可能是具有 id==1的 dog) ,而不是文档处理器。

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


设计问题的 API 调用

因此,让我们切入正题,试着通过考虑 在资源方面来设计这些树皮。请允许我引用 宁静的网络服务的书:

POST请求是尝试从现有的 现有资源可能是 从数据结构的角度来看,树的根是所有树的根 它的叶子节点。或者现有的资源可能是一个特殊的 “工厂” 资源的唯一目的是生成其他资源 随 POST请求一起发送的表示描述了初始 与 PUT 一样,POST请求不需要 包括一个代表。

按照上面的描述,我们可以看到,bark可以建模为 dog的子资源(因为 bark包含在一只狗中,也就是说,一声吠叫就是“吠叫”作者一只狗)。

从这个推理我们已经得到:

  • 方法是 POST
  • 资源是 /barks,狗的子资源: /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就足够了。它表明一切都按照预期进行。


吠叫向 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来取消。


树皮向 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。来自 关于 303的 HTTP RFC:

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

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


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(正如 HTTP RFC 所说)。

保存的时间戳是 bark资源的一部分,可以用 GET检索它。更新的狗可以“记录”在该 GET dogs/X/barks/Y以及。


4.巴克运行一个系统命令从 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

然后,客户机将向 /v1/dogs/1/barks/a65h44发出 GET,以了解当前的状态(如果提取了代码,则将电子邮件发送给所有者等)。每当狗改变,一个 303是适用的。


收工

引用 Roy Fielding:

REST 对方法的唯一要求是它们是一致的 为所有资源定义(例如,这样中介体就不必 了解资源类型,以便理解 要求)。

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

程序员应该知道: 从 POSTbarks产生 bark。响应状态代码(必要时还带有实体主体和头部)负责解释发生了什么变化以及客户机可以和应该如何进行。

注: 使用的主要来源是: “ 宁静的网络服务”书,HTTP RFCRoy Fielding 的博客




编辑:

这个问题和答案自从它们第一次被创建以来已经发生了很大的变化。原始问题询问 URI 的设计如下:

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

下面是为什么这不是一个好的选择的解释:

客户机如何告诉服务器 怎么办与数据是 方法资料

  • RESTful Web 服务在 HTTP 方法中传递方法信息。
  • 典型的 RPC 样式和 SOAP 服务将它们的服务保存在实体体和 HTTP 头中。

数据[客户机希望服务器]操作的部分 范围界定信息

  • RESTful 服务使用 URI.SOAP/RPC-Style 服务再次使用实体体和 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 订购服务,其中订单位于像 example.com/order/123这样的 URI。

现在假设我们要取消订单,我们该怎么做呢?人们可能会认为这是一个 “取消” “开拍”和设计它作为 POST example.com/order/123?do=cancel

这不是 RESTful,正如我们上面所说的。相反,我们可以使用 PUT作为 order的一个新表示,并将一个 canceled元素发送给 true:

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


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

就是这样。如果订单不能被取消,可以返回一个特定的状态码

在您的特定场景中,您可以尝试类似的东西。这样,当一只狗在叫的时候,例如,/v1/dogs/1/GET可以包含 (例如 <barking>true</barking>)的信息。或者... 如果这太复杂,放松您的 REST 需求,坚持使用 POST

更新:

我不想把答案说得太大,但是要掌握将算法(开拍)作为一组资源公开的窍门还需要一段时间。与其从动作的角度思考(“在地图上搜索地点”) ,不如从动作的结果来思考(< em > “地图上与搜索匹配的地点列表 准则」

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

查询变量 范围界定信息,但是 没有表示新的资源(/post?lang=en显然是 一样资源的 /post?lang=jp,只是不同的表示)。相反,它们用于将 /post?lang=jp0(如 ?page=10,因此该状态不保存在服务器中; 这里的 ?lang=en也是一个例子)或 /post?lang=jp1传递给 /post?lang=jp2(/search?q=dogs/dogs?code=1)。再说一次,不是特定的资源。

HTTP 谓词的(方法)属性:

显示 URI 中的 ?action=something不是 RESTful 的另一个明确点是 HTTP 动词的属性:

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

安全 : GETHEAD请求是对 某些数据的请求,而不是更改任何服务器状态的请求。客户端可以发出 GETHEAD请求10次,这与发出一次或 从来没有成功过请求是相同的。

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

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

这两个属性对于 HTTP 协议的成功(在不可靠的网络上)都很重要: 你有多少次更新(GET)的网页没有等待,直到它完全加载?

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

对一些答案的早期修订建议您使用 RPC。您不需要指望 RPC,因为它完全有可能在遵守 REST 约束的同时完成您想要的任务。

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

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

幂等意味着一个请求可以是 在不改变结果的情况下重复。。这些效果不包括日志记录和其他这样的服务器管理活动。使用你的第一个和第二个例子,发送两封电子邮件给同一个人确实会导致不同的状态比发送一封电子邮件(收件人有两个在他们的收件箱,他们可能认为是垃圾邮件) ,所以我肯定会使用 POST 的。如果示例2中的 barkCount 希望您的 API 的用户看到,或者影响客户端可见的内容,那么它也会使请求非幂等。如果它只能被您查看,那么它就算作服务器日志记录,在确定幂等性时应该忽略它。

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

当为任何东西选择 URI 时,试着把它看作一个结果,而不是一个操作。例如,google.com/search?q=dogs显示了搜索单词“ dog”的 结果。它不一定执行搜索。

列表中的情况 # 3和 # 4也不是幂等操作。您认为不同的建议效果可能会影响 API 设计。在这四种情况下,我都会使用相同的 API,因为这四种 API 都会改变“ world state”

POST设计了 HTTP 方法

为数据处理过程提供一个数据块

处理非 CRUD 映射的操作的服务器端方法就是 Roy Fielding 打算和 REST,所以您在这方面做得很好,这就是为什么 POST被设计成非幂等的原因。POST将处理大多数数据发布到服务器端的方法来处理信息。

也就是说,在您的犬吠场景中,如果您希望每10分钟执行一次服务器端的犬吠,但由于某种原因需要从客户端启动触发器,那么 PUT将更好地满足这个目的,因为它是幂等的。严格来说,在这种情况下,不存在多个 POST 请求导致狗狗喵喵叫的明显风险,但无论如何,这就是这两种类似方法的目的。我对类似 SO 问题的回答可能对你有用。

如果我们假设 Barking 是一个内部/依赖/子资源,消费者可以对其采取行动,那么我们可以说:

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

一号狗叫

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

返回最后一个树皮时间戳

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

不适用,那就忽略它。

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

这里有一个 建议,碰巧是 RESTful 的,但肯定不是唯一的选择。当服务收到请求时开始吠叫:

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

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

next表示下一次树皮的时间; 0的值表示“ ASAP”。

每当你的 GET /v1/dogs/1/bark-schedule,你应该得到这样的东西,其中 T是最后一次树皮的时间和 T + 10分钟:

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

我强烈建议您使用相同的 URL 来请求一个吠声,您可以使用这个 URL 来了解狗当前的吠声状态。REST 并不是必不可少的,但是它强调修改时间表的行为。

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

解释

休息

暂时忘记 HTTP 吧。必须理解,资源是一个以时间作为输入并返回包含 识别资料申述的集合的函数。让我们将其简化为: 资源是标识符和表示的集合 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 有用的原因: 它允许您使用 将标识符连接到表示。也就是说,它是服务接收 URL 并决定向客户端提供哪种表示的一种方式。

至少 GET是这么做的。PUT基本上与 GET相反: 如果您希望将来 GET对该 URL 的请求返回 R,那么您可以在 URL 上使用 PUT表示 R,并且可以使用一些可能的转换,比如 JSON 到 HTML。

POST是修改表示形式的一种较松散的方法。想象一下,存在相互对应的显示逻辑和修改逻辑——它们都对应于同一个 URL。POST 请求是对修改逻辑的请求,修改逻辑将处理信息并根据服务的需要修改任何表示(而不仅仅是位于同一 URL 的表示)。请注意 9.6推之后的第三段: 您不是用新内容替换 URL 上的内容; 您是要求 URL 上的内容处理一些信息,并以信息表示的形式智能地作出响应。

在我们的示例中,我们请求 /v1/dogs/1/bark-schedule中的修改逻辑(与显示逻辑相对应,后者告诉我们它上次吠叫的时间和下次吠叫的时间)处理我们的信息并相应地修改一些表示。为了响应未来的 GET,对应于相同 URL 的显示逻辑将告诉我们,狗现在正如我们所希望的那样狂吠。

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

我的答案是 早些时候回答,但是这个答案与我以前的答案相矛盾,并且遵循了一个非常不同的策略来得到一个解决方案。它展示了如何根据定义 REST 和 HTTP 的概念构建 HTTP 请求。它还使用 PATCH而不是 POSTPUT

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

休息

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

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

REST 是由四个接口约束构成的 定义:

资源的识别; 通过表示对资源的操纵; 自描述消息; 以及作为应用程序状态引擎的超媒体。

你问如何定义一个界面,满足这些约束,通过这个界面,一台计算机告诉另一台计算机让狗叫。具体来说,您希望您的接口是 HTTP,并且您不希望放弃按预期使用 HTTPRESTful 的特性。

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

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

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

更准确地说,资源 R是一个暂时变化的成员关系函数 M < sub > R (T) ,它在一段时间内映射到一组实体或值,这些实体或值是等价的。集合中的值可以是 资源表示法和/或 资源标识符

模特一只狗,通过采取一套标识符和表示,并说他们都相互关联在一个给定的时间。现在,让我们使用标识符“ dog # 1”。这就把我们带到了第二个和第三个约束: 资源表示法自我描述

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

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

自从这种状态改变生效以来,它每10分钟就吠一次,而且会无限期地继续下去。

它应该附加到描述它的元数据上,这个元数据可能有用:

这是一个英语陈述。它描述了预期状态的一部分。如果它被接收多次,只允许第一次有一个效果。

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

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

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

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

HTTP

HTTP 实现 REST 约束如下:

资源标识 : URI

资源表示 : 实体-主体

Self-description : 方法或状态代码、标头,可能还有实体主体的某些部分(例如 XML 模式的 URI)

HATEOAS : 超链接

您已经决定将 http://api.animals.com/v1/dogs/1作为 URI。

让我们使用这个实体主体(next的值是一个时间戳; 0的值表示‘当接收到这个请求时’) :

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

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

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状态码作为响应,或者如果 /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 作业视为服务器对接口隐藏的实现细节。这就是通用接口的美妙之处。客户机不需要知道服务器在幕后做什么; 它所关心的只是服务理解并响应请求的状态更改。