请记住,我对REST有基本的了解 . 假设我有这个网址:
http://api.animals.com/v1/dogs/1/
现在,我想让服务器让狗吠 . 只有服务器知道如何执行此操作 . 假设我想让它在一个CRON工作上运行,这使得狗在永恒的剩余时间内每隔10分钟就会吠叫一次 . 这个电话是什么样的?我有点想这样做:
网址请求:
ACTION http://api.animals.com/v1/dogs/1/
在请求正文中:
{"action":"bark"}
在你为了构建我自己的HTTP方法而生气之前,请帮助我,让我更好地了解如何以RESTful方式调用服务器端方法 . :)
EDIT FOR CLARIFICATION
关于“树皮”方法做什么的更多澄清 . 以下是一些可能导致不同结构化API调用的选项:
-
bark只是发送一封电子邮件给dog.email并且没有记录任何内容 .
-
bark向dog.email发送电子邮件,并将dog.barkCount增加1 .
当发生树皮时, -
bark会使用bark.timestamp记录创建一个新的"bark"记录 . 它还将dog.barkCount递增1 .
-
bark运行系统命令从Github下载最新版本的狗代码 . 然后它向dog.owner发送一条短信告诉他们新的狗代码正在制作中 .
8 回答
REST是一种面向资源的标准,它不像RPC那样是动作驱动的 .
如果您希望服务器咆哮,您应该查看不同的想法,如JSON-RPC,或者进入websockets通信 .
在我看来,每次尝试保持RESTful都会失败:你可以发出带有
action
参数的POST
,你没有创建任何新资源但是因为你可能有副作用,所以你更安全 .如果我们假设Barking是消费者可以采取行动的内部/依赖/子资源,那么我们就可以说:
狗1号吠叫
返回最后一个树皮时间戳
不适用!所以忽略它 .
为什么要设计RESTful设计?
RESTful原则 bring the features that make web sites easy (对于一个随机的人类用户来说"surf") to the web services API design ,因此它们很容易让程序员使用 . REST isn't good because it's REST, it's good because it's good.这很好,主要是因为它是 simple .
普通HTTP的简单性(没有SOAP信封和单URI重载的
POST
服务), some may call "lack of features" ,实际上是 its greatest strength . 最后,HTTP要求您具有可寻址性和无状态性:两个基本的设计决策使HTTP可以扩展到今天的大型站点(和大型服务) .但REST不是银色的bulltet: Sometimes an RPC-style ("Remote Procedure Call" - 比如SOAP) may be appropriate ,有时其他需求优先于Web的优点 . 这可以 . 我们真的不喜欢 is needless complexity . 程序员或公司常常为普通旧HTTP可以正常处理的作业引入RPC样式的服务 . 结果是,HTTP被简化为一个巨大的XML有效负载的传输协议,解释了什么's 2843311 going on (not the URI or the HTTP method give a clue about it). The resulting service is far too complex, impossible to debug, and won' t工作,除非您的客户端有开发人员想要的 exact setup .
同样,Java / C#代码也不是面向对象的,只是使用HTTP并不能使设计成为RESTful . 人们可能会在 thinking 关于他应该被召唤的服务 in terms of actions and remote methods 的热潮中陷入困境 . 难怪这将主要以RPC样式服务(或REST-RPC-hybrid)结束 . 第一步是思考不同 . RESTful设计可以通过多种方式实现,一种方式(最简单,有些人可能会说)是 think of your application in terms of resources, not actions:
而不是考虑行动("do a search for places on the map"),
根据该行动的结果进行思考("the list of places on the map matching a search criteria") .
我在这里刷它,但我很快就谈起来了.2843320_ . )
关于第一个设计
我们来看看拟议的设计:
首先,我们不应该考虑创建 new HTTP verb (
ACTION
) . 一般来说,这是不可取的,原因如下:(1) 只给出服务URI,"random"程序员将如何知道
ACTION
动词是否存在?(2) 如果程序员知道它存在,他将如何知道它的语义?这个动词是什么意思?
(3) 人们应该期望动词有什么属性(安全性,幂等性)?
(4) 如果程序员有一个只处理标准HTTP动词的非常简单的客户端怎么办?
(5) ...
现在让我们 consider using POST (我将在下面讨论为什么,现在就接受我的话):
这可能没问题但是 only if :
{"action":"bark"}
是一份文件;和/v1/dogs/1/
是"document processor"(类似工厂)URI . “文档处理器”是一个URI,你只是“扔东西”并“忘记”它们 - 处理器可能会在“抛出”之后将你重定向到新创建的资源 . 例如 . 用于在消息代理服务上发布消息的URI,在发布之后会将您重定向到显示消息处理状态的URI .我对你的系统了解不多,但我已经打赌两者都不是真的:
{"action":"bark"}
is not a document ,它实际上是 is the method 你正试图忍者偷偷进入服务;和/v1/dogs/1/
URI表示"dog"资源(可能是带有id==1
的狗)而不是文件处理器 .所以我们现在所知道的是上面的设计并不那么RESTful,但究竟是什么呢? What is so bad about it? 基本上,它很糟糕,因为这是具有复杂含义的复杂URI . 你无法从中推断出任何东西 . 一个程序员怎么会知道一条狗有
bark
动作可以秘密地注入其中POST
?设计问题的API调用
所以,让我们切入追逐,并尝试通过思考 in terms of resources 来设计那些吠叫 . 请允许我引用Restful Web Services书:
根据上面的描述,我们可以看到 bark 可以被建模为 a subresource of a dog (因为
bark
包含在狗中,也就是说,树皮是"barked" by 狗) .从这个推理我们已经得到:
该方法是
POST
资源是
/barks
,dog的子资源:/v1/dogs/1/barks
,表示bark
"factory" . 该URI对于每只狗都是唯一的(因为它在/v1/dogs/{id}
之下) .现在,列表中的每个案例都有特定的行为 .
1. bark只是发送一封电子邮件给dog.email并且没有记录任何内容 .
首先,咆哮(发送电子邮件)同步或异步任务?其次
bark
请求是否需要任何文件(可能是电子邮件)还是空的?1.1 bark向dog.email发送电子邮件并且不记录任何内容(作为同步任务)
这种情况很简单 . 对
barks
工厂资源的调用会立即产生树皮(发送电子邮件),并立即给出响应(如果可以或不存在):因为它记录(更改)没有,
200 OK
就足够了 . 它表明一切都按预期进行 .1.2 bark向dog.email发送电子邮件并且不记录任何内容(作为异步任务)
在这种情况下,客户端必须能够跟踪
bark
任务 . 然后bark
任务应该是具有自己的URI的资源:这样,每个
bark
都是可追踪的 . 然后,客户端可以向bark
URI发出GET
以了解其当前状态 . 甚至可以使用DELETE
取消它 .2. bark向dog.email发送电子邮件,然后将dog.barkCount增加1
如果您想让客户端知道
dog
资源被更改,那么这个可能会更棘手:在这种情况下,
location
Headers 的目的是让客户知道他应该看看dog
. 来自HTTP RFC about 303:如果任务是异步的,则需要
bark
子资源,就像1.2
情况一样,并且当任务完成时GET .../barks/Y
应该在GET .../barks/Y
返回 .当树皮发生时,be2 3. bark会在bark.timestamp记录时创建一个新的“树皮”记录 . 它还将dog.barkCount递增1 .
在此处,
bark
是由于请求而创建的,因此应用了状态201 Created
.如果创建是异步的,则需要
202 Accepted
(as the HTTP RFC says) .保存的时间戳是
bark
资源的一部分,可以使用GET
检索它 . 更新的狗也可以在GET dogs/X/barks/Y
中"documented" .4. bark运行系统命令从Github下载最新版本的狗代码 . 然后它向dog.owner发送一条短信告诉他们新的狗代码正在制作中 .
这个的措辞很复杂,但它几乎是一个简单的异步任务:
然后客户端将
GET
发送到/v1/dogs/1/barks/a65h44
以了解当前状态(如果代码已被提取,则电子邮件已发送给所有者等) . 每当狗发生变化时,都可以使用.2843397_ .结束
引用Roy Fielding:
在上面的例子中,
POST
是统一设计的 . 它会使狗“bark
” . 这不安全(意味着树皮对资源有影响),也不是幂等(每个请求产生一个新的bark
),这很好地符合POST
动词 .程序员会知道:
POST
到barks
会产生bark
. 响应状态代码(必要时还包含实体主体和 Headers )可以解释更改的内容以及客户端可以和应该如何进行 .注意:使用的主要来源是:“Restful Web Services”一书,HTTP RFC和Roy Fielding的博客 .
Edit:
自问题首次创建以来,问题和答案已经发生了很大的变化 . original question 询问了如下设计:
下面解释为什么它不是一个好的选择:
客户端如何使用数据告诉服务器 WHAT TO DO 是方法信息 .
RESTful Web服务在HTTP方法中传递方法信息 .
典型的RPC-Style和SOAP服务将它们保存在entity-body和HTTP头中 .
_ [8843410_] [客户端希望服务器]操作的数据是作用域信息 .
例如,请使用Google的URI
http://www.google.com/search?q=DOG
. 在那里,方法信息是GET
,范围信息是/search?q=DOG
.长话短说:
在 RESTful architectures 中,方法信息进入HTTP方法 .
在 Resource-Oriented Architectures 中,作用域信息进入URI .
而经验法则:
您可以将"bark" "action"放入URL(或实体主体中)并使用
POST
. 没问题,它有效,可能是最简单的方法, but this isn't RESTful .为了使您的服务真正保持RESTful,您可能不得不退后一步思考您在这里真正想做的事情(它会对资源产生什么影响) .
我不能谈谈您的具体业务需求,但让我举个例子:考虑一个RESTful订购服务,其中订单位于
example.com/order/123
之类的URI .现在说我们要取消订单,我们将如何做?有人可能会认为这是"cancellation" "action"并将其设计为
POST example.com/order/123?do=cancel
.正如我们上面谈到的那样,这不是RESTful . 相反,我们可能
PUT
order
的新表示,其中canceled
元素被发送到true
:并且's it. If the order can' t被取消,可以返回特定的状态代码 . (为简单起见,子资源设计,如POST / order / 123 /使用entity-body true取消,也可以使用 . )
在您的特定场景中,您可以尝试类似的东西 . 这样,当狗正在吠叫时,
/v1/dogs/1/
可以包含该信息(例如<barking> true </ barking>) . 或者......如果这太复杂了,放松你的RESTful要求并坚持使用POST
.更新:
我不想让答案太大,但需要一段时间才能将算法(一个动作)作为一组资源暴露出来 . 不需要考虑行动("do a search for places on the map"),而是需要根据行动的结果进行思考("the list of places on the map matching a search criteria") .
如果您发现您的设计不适合HTTP的统一界面,您可能会发现自己回到这一步 .
查询变量 are 范围信息,但是 not 表示新资源(
/post?lang=en
显然是 same 资源为/post?lang=jp
,只是一个不同的表示) . 相反,它们用于传达 client state (如?page=10
,因此状态不保留在服务器中;?lang=en
也是此处的示例)或 input parameters 到算法资源(/search?q=dogs
,/dogs?code=1
) . 同样,不是不同的资源 .HTTP动词'(方法)属性:
另一个显示URI中
?action=something
不是RESTful的明确点是HTTP谓词的属性:GET
和HEAD
是安全的(和幂等的);PUT
和DELETE
仅是幂等的;POST
既不是 .Safety :
GET
或HEAD
请求是对某些数据的请求,而不是更改任何服务器状态的请求 . 客户端可以发出10次GET
或HEAD
请求,这与进行一次或从不进行任何操作相同 .Idempotence :幂等操作一个具有相同效果的操作,无论是一次还是多次应用(在数学中,乘以零是幂等的) . 如果您资源一次,则再次删除将具有相同的效果(资源已经是
GONE
) .POST既不安全也不是幂等 . 对“工厂”资源发出两个相同的POST请求可能会导致两个下级资源包含相同的信息 . 随着重载(URI或实体体中的方法)POST,所有赌注都关闭 .
这两个属性对于HTTP协议的成功非常重要(在不可靠的网络上!):您有多少次更新(
GET
)页面而不等到它完全加载?创建一个动作并将其放在URL中会明显破坏HTTP方法的 Contract . 再一次,技术允许你,你可以做到,但这不是RESTful设计 .
I answered earlier, but this answer contradicts my old answer and follows a much different strategy for coming to a solution. 它显示了如何根据定义REST和HTTP的概念构建HTTP请求 . 它还使用
PATCH
而不是POST
或PUT
.它通过REST约束,然后是HTTP的组件,然后是一个可能的解决方案 .
REST
REST是一组旨在应用于分布式超媒体系统的约束,以使其可扩展 . 即使在远程控制动作的上下文中理解它,您也必须考虑将动作远程控制为分布式超媒体系统的一部分 - 分布式超媒体系统是发现,查看和修改互连信息的系统的一部分 . 如果这比它的 Value 更麻烦,那么尝试使它成为RESTful可能并不好 . 如果您只想在客户端上使用“控制面板”类型的GUI,它可以通过端口80触发服务器上的操作,那么您可能需要一个简单的RPC接口,如通过HTTP请求/响应的JSON-RPC或WebSocket .
但REST是一种迷人的思维方式,问题中的示例恰好可以通过RESTful界面轻松建模,因此让我们接受有趣和教育的挑战 .
REST是由两个接口约束的defined:
你问如何定义一个接口,满足这些约束,一台计算机通过它告诉另一台计算机让狗叫 . 具体来说,您希望您的接口是HTTP,并且您不希望在按预期使用时丢弃使HTTP RESTful成为的功能 .
让我们从第一个约束开始: resource identification .
所以狗是一种资源 . 需要确定 .
您通过采用一组标识符和表示形式对狗进行建模,并说它们在给定时间彼此相关联 . 现在,让我们使用标识符"dog #1" . 这带来了第二和第三个约束: resource representation 和 self-description .
以下是捕获狗的预期状态的字节序列,即我们希望与标识符“dog#1”相关联的表示(注意它仅代表状态的一部分,因为它不考虑狗的名字, Health ,甚至过去的吠声):
它应该附加到描述它的元数据 . 此元数据可能很有用:
最后,让我们看看第四个约束: HATEOAS .
在RESTful接口中,客户端接收资源表示以便弄清楚它应该如何接收或发送表示 . 在应用程序的某处必须有一个表示,客户端可以从中找出如何接收或发送它应该能够接收或发送的所有表示,即使它遵循一系列表示来获得该信息 . 这看起来很简单:
客户端要求表示被识别为主页的资源;作为响应,它获得一个表示,其中包含客户端可能想要的每只狗的标识符 . 客户端从中提取标识符并询问服务如何与识别的狗进行交互,并且服务说客户端可以发送描述狗的预期状态的部分英语声明 . 然后客户端发送这样的语句并接收成功消息或错误消息 .
HTTP
HTTP实现REST约束,如下所示:
resource identification :URI
resource representation :entity-body
self-description :方法或状态代码,标头以及实体主体的可能部分(例如XML模式的URI)
HATEOAS :超链接
你决定使用
http://api.animals.com/v1/dogs/1
作为URI . 让我们假设客户端从网站上的某个页面获取此信息 .让我们使用这个实体 - 体(
next
的值是时间戳;值0
表示'when this request is received'):现在我们需要一种方法 . PATCH符合我们决定的"part of the intended state"描述:
还有一些 Headers :
表明实体主体的语言:
Content-Type: application/json
确保它只发生一次:
If-Unmodified-Since: <date/time this was first sent>
我们有一个要求:
成功时,客户端应收到204状态代码作为响应,如果
/v1/dogs/1/
的表示已更改以反映新的吠叫时间表,则应收到205
.失败时,它应该收到
403
和有用的消息原因 .REST服务在表示中响应
GET /v1/dogs/1/
反映树皮调度并不是必需的,但如果JSON表示包含此内容则最有意义:将cron作业视为服务器从界面隐藏的实现细节 . 这就是通用界面的美妙之处 . 客户端不必知道服务器在幕后做了什么;它所关心的只是服务理解并响应所请求的状态变化 .
大多数人为此目的使用POST . 适合执行"any unsafe or nonidempotent operation when no other HTTP method seems appropriate" .
诸如XMLRPC之类的API使用POST来触发可以运行任意代码的操作 . "action"包含在POST数据中:
RPC的示例是为了表明POST是服务器端方法的传统HTTP谓词选择 . 这是Roy Fielding thoughts on POST - 他几乎说使用指定的HTTP方法是RESTful .
请注意,RPC本身并不是面向资源的 . 但是如果你需要无状态,缓存或分层,那么进行适当的转换并不困难 . 有关示例,请参阅http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ .
POST
是HTTP method designed for处理非CRUD映射操作的服务器端方法是Roy Fielding intended与REST,因此're good there, and that'为什么
POST
被认为是非幂等的 .POST
将处理大多数数据发布到服务器端方法以处理信息 .也就是说,在你的狗吠情景中,如果你想要每10分钟执行一次服务器端的吠叫,但由于某种原因需要触发器来自客户端,因为它的幂等性,它会更好地服务于目的 . 那么,严格按照这种情况,两个类似方法的目的就是's no apparent risk of multiple POST requests causing your dog to meow instead, but anyway that' . My answer to a similar SO question可能对您有用 .
一些答案的早期修订建议您使用RPC . 您不需要查看RPC,因为在遵守REST约束的情况下完全可以执行您想要的操作 .
首先,不要在URL中放置动作参数 . URL定义了您应用操作的内容,查询参数是URL的一部分 . 它应该完全被认为是 noun.
http://api.animals.com/v1/dogs/1/?action=bark
是一个不同的资源 - 一个不同的名词 - 到http://api.animals.com/v1/dogs/1/
. [注: Asker已从问题中删除了?action=bark
URI . ]例如,将http://api.animals.com/v1/dogs/?id=1
与http://api.animals.com/v1/dogs/?id=2
进行比较 . 不同的资源,仅由查询字符串区分 . 因此,您的请求的操作必须在请求正文中定义,除非它直接对应于无体的现有方法类型(TRACE,OPTIONS,HEAD,GET,DELETE等) .接下来,确定该动作是否为“idempotent”,意味着它可以重复而没有不利影响(有关更多说明,请参阅下一段) . 例如,如果客户端不确定发生了所需的效果,则可以重复将值设置为true . 他们再次发送请求,值仍然为真 . 将1添加到数字不是幂等的 . 如果客户端发送Add1命令,则isn 't sure it worked, and sends it again, did the server add one or two? Once you have determined that, you'在更好的位置为您的方法选择
PUT
和POST
.幂等意味着可以在不改变结果的情况下重复请求 . 这些影响不包括日志记录和其他此类服务器管理活动 . 使用您的第一个和第二个示例,向同一个人发送两封电子邮件会导致与发送一封电子邮件不同的状态(收件人在收件箱中有两封电子邮件,他们可能认为这是垃圾邮件),所以我肯定会使用POST . 如果示例2中的barkCount旨在被API的用户看到或影响客户端可见的内容,那么它也会使请求不是幂等的 . 如果它只是由您查看,那么它将被视为服务器日志记录,在确定幂等性时应该被忽略 .
最后,确定您希望执行的操作是否可以立即成功 . BarkDog是一个快速完成的行动 . RunMarathon不是 . 如果您的操作很慢,请考虑返回
202 Accepted
,并在响应正文中使用URL以供用户轮询以查看操作是否完成 . 或者,让用户POST到列表URL,如/marathons-in-progress/
,然后在操作完成后,将它们从进行中ID URL重定向到/marathons-complete/
URL .对于特定情况#1和#2,我将让服务器主持一个队列,并且客户端将批量地址发布到它 . 该操作不是SendEmails,而是类似AddToDispatchQueue . 然后,服务器可以轮询队列以查看是否有任何电子邮件地址等待,并在发现任何电子邮件时发送电子邮件 . 然后,它会更新队列以指示现在已执行挂起操作 . 您将有另一个URI显示客户端队列的当前状态 . 为了避免重复发送电子邮件,服务器还可以记录发送此电子邮件的人员,并检查每个地址,以确保它永远不会将两个地址发送到同一地址,即使您将相同的列表两次发送到队列 .
在为任何事物选择URI时,请尝试将其视为结果,而不是动作 . 例如,
google.com/search?q=dogs
显示搜索单词"dogs"的 results . 它没有必要执行搜索 .列表中的案例#3和#4也不是幂等行为 . 您建议不同的建议效果可能会影响API设计 . 在所有四种情况下,我都会使用相同的API,因为所有四种情况都会改变“世界状态” .
See my new answer - 它与此相矛盾,更清楚准确地解释了REST和HTTP .
这是一个恰好是RESTful的 recommendation ,但肯定不是唯一的选择 . 在服务收到请求时开始吠叫:
token
是一个任意数字,无论发送此请求多少次都可以防止冗余吠叫 .next
表示下一次吠叫的时间;值0
表示'ASAP' .每当你
GET /v1/dogs/1/bark-schedule
,你应该得到这样的东西,其中t是最后一次吠叫的时间,你是10分钟:{"last": t, "next": u}
我强烈建议您使用相同的URL来请求您使用的树皮来了解狗当前的吠叫状态 . 它不是REST必不可少的,但是它强调修改时间表的行为 .
适当的状态代码可能是205 . 我想象一个客户端查看当前的时间表,
POST
到同一个URL进行更改,并且服务人员指示他们再次查看时间表以证明它已被更改 .解释
REST
暂时忘掉HTTP . 理解resource是一个需要时间作为输入并返回包含标识符和表示的集合的函数是很重要的 . 让我们简化为:资源是标识符和表示的集合R; R可以更改 - 可以添加,删除或修改成员 . (虽然删除或修改标识符是不好的,不稳定的设计 . )我们说作为R的元素的标识符标识R,并且作为R的元素的表示表示R.
让我们说R是一只狗 . 您碰巧将R识别为
/v1/dogs/1
. (含义/v1/dogs/1
是R的成员 . )这只是您识别R的众多方法之一 . 您还可以将R识别为/v1/dogs/1/x-rays
和/v1/rufus
.你怎么代表R?也许带着照片 . 也许用一组X射线 . 或者可能指出R最后一次咆哮的日期和时间 . 但请记住,这些都是同一资源的表示 .
/v1/dogs/1/x-rays
是同一资源的标识符,由“R last last bark?”问题的答案表示 .HTTP
资源的多个表示不是指您想要的表示 . 这就是HTTP有用的原因:它让你connect identifiers to representations . 也就是说,它是服务接收URL并决定向客户端提供哪种表示的一种方式 .
至少,这就是
GET
所做的 .PUT
基本上与GET
相反:如果您希望将来的GET
请求返回到r,并且有一些可能的转换(如JSON到HTML),那么您将在URL处使用表示符号 .POST
是一种修改表示的宽松方式 . 可以想象存在彼此对应的显示逻辑和修改逻辑 - 两者都对应于相同的URL . POST请求是对修改逻辑的请求,以处理信息并修改服务认为合适的任何表示(不仅仅是由同一URL定位的表示) . 注意_2843554之后的第三段:你要求URL上的东西处理一些信息并以信息表示的形式智能地回应 .在我们的例子中,我们要求
/v1/dogs/1/bark-schedule
处的修改逻辑(它是显示逻辑的对应部分,告诉我们它何时最后一次吠叫以及何时接下来会吠叫)来处理我们的信息并相应地修改一些表示 . 为了响应未来GET
,对应于相同URL的显示逻辑将告诉我们狗正在按我们的意愿吠叫 .将cron作业视为实现细节 . HTTP处理查看和修改表示 . 从现在开始,该服务将告诉客户当狗最后一次吠叫以及它何时会吠叫 . 从服务的角度来看,这是诚实的,因为那些时间与过去和计划的cron工作相对应 .