为什么要以 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 动词。
程序员会知道:POST 到 barks 会产生 bark。响应状态代码(必要时还带有实体主体和标头)用于解释发生了什么变化以及客户端可以和应该如何继续。
注意:使用的主要来源是:“Restful Web Services”书、HTTP RFC 和Roy 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。相反,我们可以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>
就是这样。如果订单无法取消,可以返回特定的状态码。 (为简单起见,也可以使用带有实体主体true 的子资源设计,例如POST /order/123/canceled。)
在您的特定情况下,您可以尝试类似的方法。这样,例如,当狗在吠叫时,/v1/dogs/1/ 上的 GET 可以包含该信息(例如 <barking>true</barking>)。或者......如果这太复杂了,放宽你的 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 动词的属性:
-
GET 和 HEAD 是安全的(并且是幂等的);
-
PUT 和 DELETE 仅是幂等的;
-
POST 两者都不是。
安全:GET 或 HEAD 请求是读取某些数据的请求,而不是更改任何服务器状态的请求。客户端可以发出 10 次 GET 或 HEAD 请求,这与发出一次相同,或者根本不发出。
幂等性:一种幂等运算,无论您应用一次还是多次都具有相同的效果(在数学中,乘以零是幂等的)。如果你DELETE一个资源一次,再次删除也会有同样的效果(资源已经是GONE了)。
POST 既不安全也不幂等。向“工厂”资源发出两个相同的POST 请求可能会导致两个从属资源包含相同的
信息。重载(URI 或实体主体中的方法)POST,所有赌注都关闭了。
这两个属性对于 HTTP 协议的成功都很重要(通过不可靠的网络!):您有多少次更新 (GET) 页面而不等到页面完全加载?
创建一个 action 并将其放置在 URL 中显然违反了 HTTP 方法的约定。再一次,技术允许你,你可以做到,但这不是 RESTful 设计。