为什么要追求 RESTful 设计?
RESTful 原则带来使网站变得简单的功能 (for a 随机人类用户来“冲浪”它们)Web 服务 API 设计,因此程序员很容易使用它们。REST 并不因为它是 REST 而好,而是因为它好所以好。 http://www.tbray.org/ongoing/When/200x/2009/03/20/Rest-Casuistry它很好主要是因为它是simple.
纯 HTTP 的简单性(没有 SOAP 信封和单 URI 重载)POST
服务),什么有些人可能会打电话“缺乏特色”, 实际上是它最大的力量。 HTTP 立即要求您拥有可寻址性 and 无国籍:保持 HTTP 可扩展至当今大型站点(和大型服务)的两个基本设计决策。
但 REST 并不是灵丹妙药:有时是 RPC 风格(“远程过程调用”——例如 SOAP)可能合适,有时其他需求优先于网络的优点。这可以。我们真正不喜欢的是不必要的复杂性。很多时候,程序员或公司会引入 RPC 风格的服务来完成普通的旧 HTTP 可以很好处理的工作。其效果是,HTTP 被简化为用于解释“真正”发生的情况的巨大 XML 负载的传输协议(而不是 URI 或 HTTP 方法提供有关它的线索)。由此产生的服务过于复杂,无法调试,并且除非您的客户拥有精确设置正如开发商的意图。
与 Java/C# 代码相同not面向对象,仅使用 HTTP 并不能使设计成为 RESTful。一个人可能会陷入匆忙之中thinking关于他们的服务在操作和远程方法方面应该这样称呼。难怪这大部分最终都会出现在 RPC 风格的服务(或 REST-RPC 混合服务)中。第一步是以不同的方式思考。 RESTful 设计可以通过多种方式实现,其中一种方法是从资源而不是操作的角度考虑您的应用程序:
???? 不要考虑它可以执行的操作(“在地图上搜索地点”)......
...尝试从以下角度思考results这些操作的列表(“地图上符合搜索条件的地点列表”)。
我将在下面举一些例子。
(REST的另一个关键方面是HATEOAS的使用——这里就不刷了,不过我很快就讲一下在另一个帖子 https://stackoverflow.com/a/30607633/1850609.)
第一个设计的问题
让我们看一下建议的设计:
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"}
This could没关系...但是only if:
-
{"action":"bark"}
是一份文件;和
-
/v1/dogs/1/
was a "document processor" (factory-like) URI. A "document processor" is a URI that you'd just "throw things at" and "forget" about them - the processor may redirect you to a newly created resource after the "throwing". E.g. the URI for posting messages at a message broker service, which, after the posting would redirect you to a URI that shows the status of the message's processing.
我对你的系统了解不多,但我打赌这两者都不是真的:
-
{"action":"bark"}
不是一个文件,实际上是方法你正在尝试忍者潜行进入服务;和
- the
/v1/dogs/1/
URI 代表“狗”资源(可能是带有id==1
)而不是文档处理器。
所以我们现在所知道的是,上面的设计并不是那么 RESTful,但这到底是什么呢?这有什么不好呢?基本上,它很糟糕,因为这是具有复杂含义的复杂 URI。你无法从中推断出任何东西。程序员怎么知道狗有bark
可以秘密注入的行动POST
进去?
设计问题的 API 调用
因此,让我们切入正题,尝试通过思考来轻松地设计这些吠声在资源方面。请允许我引用宁静的网络服务 https://rads.stackoverflow.com/amzn/click/com/0596529260 book:
A POST
请求是尝试从现有资源创建新资源
一。现有资源可能是新资源的父资源
数据结构意义上,树的根是所有树的父节点
它的叶节点。或者现有的资源可能是特殊的“工厂”资源,其唯一目的是生成其他资源。这
连同一份代表一起发送POST
请求描述了初始
新资源的状态。与 PUT 一样,POST
请求不需要
完全包括代表。
根据上面的描述我们可以看出bark
可以建模为的子资源dog
(因为bark
包含在狗体内,即“吠叫”by a dog).
从这个推理我们已经得到:
- 方法是
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
是可追溯的。然后客户端可以发出GET
to the bark
用于了解当前状态的 URI。也许甚至可以使用DELETE
取消它。
2. bark 发送电子邮件至dog.email
然后递增dog.barkCount
by 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 https://www.rfc-editor.org/rfc/rfc2616#section-10.3.4:
该方法的存在主要是为了允许输出POST
- 激活脚本将用户代理重定向到选定的资源。
如果任务是异步的,bark
就像需要子资源一样1.2
情况和303
应返回GET .../barks/Y
当任务完成时。
3.树皮创建一个新的“bark
” 记录与bark.timestamp
记录吠叫发生的时间。也随之递增dog.barkCount
by 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
是必须的 (正如 HTTP RFC 所说 https://www.rfc-editor.org/rfc/rfc2616#section-10.2.2) 反而。
保存的时间戳是的一部分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 to /v1/dogs/1/barks/a65h44
了解当前状态(如果代码被提取,则电子邮件已发送给所有者等)。每当狗发生变化时,303
是适用的。
包起来
Quoting 罗伊·菲尔丁 http://roy.gbiv.com/untangled/2009/it-is-okay-to-use-post:
REST 对方法的唯一要求是它们是统一的
为所有资源定义(即,这样中介就不必
了解资源类型以便理解其含义
要求)。
在上面的例子中,POST
是统一设计的。会让狗“bark
“。这不安全(意味着树皮对资源有影响),也不是幂等的(每个请求都会产生一个新的bark
),这符合POST
动词 好。
程序员会知道:POST
to barks
产生一个bark
。响应状态代码(必要时还包含实体主体和标头)负责解释更改的内容以及客户端可以和应该如何继续。
Note: The primary sources used were: "Restful Web Services https://rads.stackoverflow.com/amzn/click/com/0596529260" book, the HTTP RFC https://www.rfc-editor.org/rfc/rfc2616 and Roy Fielding's blog http://roy.gbiv.com/untangled.
Edit:
自最初创建以来,问题和答案已经发生了很大变化。这原问题询问 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
.
长话短说:
- In RESTful 架构,方法信息进入HTTP方法。
- In 面向资源的架构,范围信息进入 URI。
经验法则:
如果 HTTP 方法与方法信息不匹配,则该服务不是 RESTful。如果范围信息不在 URI 中,则该服务不是面向资源的。
你可以把"bark" "action"在 URL(或实体主体)中并使用POST
。没问题,它有效,而且可能是最简单的方法,但这并不宁静.
为了让您的服务真正保持 RESTful,您可能必须退一步思考您真正想在这里做什么(它将对资源产生什么影响)。
我无法谈论您的具体业务需求,但让我给您举一个例子:考虑一个 RESTful 订购服务,其中订单位于 URI 处,例如example.com/order/123
.
现在假设我们想取消订单,我们该怎么做呢?人们可能会认为这是一个“消除” "action"并将其设计为POST example.com/order/123?do=cancel
.
正如我们上面所说,这不是 RESTful。相反,我们可以PUT
的新代表order
with a canceled
元素发送到true
:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
And that's it. If the order can't be canceled, a specific status code can be returned. (A subresource design, like POST /order/123/canceled
with the entity-body true
may, for simplicity, also be available.)
In your specific scenario, you may try something similar. That way, while a dog is barking, for example, a GET
at /v1/dogs/1/
could include that information (e.g. <barking>true</barking>
). Or... if that's too complicated, loosen up your RESTful requirement and stick with POST
.
Update:
我不想让答案太大,但是需要一段时间才能掌握公开算法的窍门(一个action)作为一组资源。而不是从行动的角度思考(“在地图上搜索地点”),需要考虑该行动的结果(“地图上与搜索匹配的地点列表
标准”).
如果您发现您的设计不符合 HTTP 的统一接口,您可能会发现自己又回到了这一步。
查询变量are 范围信息,但是做not表示新资源(/post?lang=en
显然是same资源为/post?lang=jp
,只是不同的表示)。相反,它们是用来传达客户状态 (like ?page=10
,这样状态就不会保存在服务器中;?lang=en
这里也是一个例子)或输入参数 to 算法资源 (/search?q=dogs
, /dogs?code=1
)。同样,不是不同的资源。
HTTP 动词(方法)属性:
另一个明确的点表明?action=something
URI 中的非 RESTful 是 HTTP 动词的属性:
-
GET
and HEAD
是安全的(并且是幂等的);
-
PUT
and DELETE
仅幂等;
-
POST
两者都不是。
Safety: A GET
or HEAD
请求是一个请求read一些数据,而不是更改任何服务器状态的请求。客户可以做一个GET
or HEAD
请求 10 次,与发出一次相同,或者从来没有成功过.
幂等性:一种幂等运算,无论应用一次还是多次,都具有相同的效果(在数学中,乘以零是幂等的)。如果你DELETE
资源一次,再次删除效果相同(资源为GONE
已经)。
POST
is neither safe nor idempotent. Making two identical POST
requests to a 'factory' resource will probably result in two subordinate resources containing the same
information. With overloaded (method in URI or entity-body) POST
, all bets are off.
这两个属性对于 HTTP 协议的成功非常重要(在不可靠的网络上!):您更新了多少次(GET
)页面无需等到完全加载?
创建一个action将其放在 URL 中显然违反了 HTTP 方法的约定。再说一次,技术允许你,你可以做到,但这不是 RESTful 设计。