为什么要针对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
- 资源是
/barks
dog:的子资源/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
都是可追溯的。然后,客户端可以GET
向bark
URI 发出A ,以了解其当前状态。甚至可以使用a DELETE
取消它。
2.树皮向发送电子邮件 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关于303
:
存在此方法主要是为了允许POST
激活脚本的输出
将用户代理重定向到所选资源。
如果任务是异步的,bark
则就像1.2
情况一样需要一个子资源,并且303
应该GET .../barks/Y
在任务完成时在a返回该资源。
3.树皮会创建新的记录,并在树皮发生时bark
进行bark.timestamp
记录。它也增加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
则需要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
然后,客户端将发出GET
s以/v1/dogs/1/barks/a65h44
了解当前状态(如果将代码拉出,则将电子邮件发送给所有者等)。每当狗改变时,a 303
都是适用的。
包起来
引用罗伊·菲尔丁(Roy Fielding):
REST对方法的唯一要求是为所有资源统一定义它们(即,中介程序不必了解资源类型即可理解请求的含义)。
在以上示例中,POST
是统一设计的。它将使狗成为“ bark
”。这是不安全的(意味着树皮会对资源产生影响),也不是幂等的(每个请求都产生一个new bark
),这很适合POST
动词。
程序员会知道:一个POST
以barks
收益率bark
。响应状态代码(必要时还包含实体主体和标头)用于解释更改内容以及客户端可以并且应该如何进行。
注意:使用的主要来源是: Restful Web Services ”书,HTTP RFC和Roy 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
表示的新表示形式:order
canceled
true
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
。)
在您的特定情况下,您可以尝试类似的操作。这样,例如,当狗吠叫时,GET
at /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=something
URI 中显示的另一个清晰点不是RESTful,是HTTP动词的属性:
GET
并且HEAD
是安全的(和幂等的);
PUT
并且DELETE
仅是幂等的;
POST
两者都不是。
安全性:GET
或HEAD
请求是读取某些数据的请求,而不是更改任何服务器状态的请求。客户端可以发出GET
或HEAD
请求10次,这与发出一次或完全不发出相同。
幂等:一次执行一次或多次都具有相同效果的幂等运算(在数学上,乘以零就是幂等)。如果您DELETE
一次使用资源,则再次删除将具有相同的效果(该资源GONE
已存在)。
POST
是安全的也不是幂等的。POST
对“工厂”资源进行两个相同的请求可能会导致两个从属资源包含相同的信息。重载(URI或实体正文中的方法)时POST
,所有投注均关闭。
这两个属性对于HTTP协议(通过不可靠的网络!)的成功非常重要:您更新了多少次(GET
)页面而没有等待页面完全加载?
创建动作并将其放置在URL中显然会破坏HTTP方法的约定。再一次,该技术允许您执行此操作,但这不是RESTful设计。