以 RESTful 方式递增资源计数器:PUT 与 POST
Posted
技术标签:
【中文标题】以 RESTful 方式递增资源计数器:PUT 与 POST【英文标题】:Incrementing resource counter in a RESTful way: PUT vs POST 【发布时间】:2010-11-28 10:48:37 【问题描述】:我有一个有计数器的资源。举个例子,我们将资源称为 profile,计数器是该资源的 views 数量个人资料。
根据REST wiki,PUT 请求应该用于资源创建或修改,并且应该是幂等的。如果我要更新配置文件的名称,这种组合很好,因为我可以发出一个 PUT 请求,将名称设置为 1000 次,结果不会改变。
对于这些标准 PUT 请求,我让浏览器执行以下操作:
PUT /profiles/123?property=value&property2=value2
为了增加一个计数器,可以这样调用 url:
PUT /profiles/123/?counter=views
每次调用都会导致计数器递增。从技术上讲,这是一个更新操作,但它违反了幂等性。
我正在寻找指导/最佳做法。您只是将其作为 POST 进行吗?
【问题讨论】:
【参考方案1】:我认为正确的答案是使用 PATCH。我没有看到其他人建议应该使用它来原子地递增计数器,但我相信 RFC 2068 说得很好:
PATCH 方法类似于 PUT,只是实体包含一个 资源原始版本之间的差异列表 由 Request-URI 和资源的所需内容标识 应用 PATCH 操作后。差异列表是 以实体的媒体类型定义的格式(例如, "application/diff") 并且必须包含足够的信息以允许 服务器重新创建转换原始文件所需的更改 资源的版本到所需的版本。
所以,要更新配置文件 123 的观看次数,我会:
PATCH /profiles/123 HTTP/1.1
Host: www.example.com
Content-Type: application/x-counters
views + 1
x-counters
媒体类型(我刚刚编写)由多行 field operator scalar
元组组成。 views = 500
或 views - 1
或 views + 3
在语法上都是有效的(但在语义上可能会被禁止)。
我可以理解一些对编造另一种媒体类型的不满,但我谦虚地建议它比 POST / PUT 替代方案更正确。为一个字段构建一个资源,包含它自己的 URI,尤其是它自己的详细信息(我并没有真正保留,我只有一个整数)对我来说听起来是错误的和麻烦的。如果我要维护 23 个不同的计数器怎么办?
【讨论】:
有点偏离标准,因为没有“应用 PATCH 操作后资源的所需内容”。例如。实体是要执行的指令,而不是期望的结果。 继@Pocketsand 所说的之后,这种方法是否不违反“统一接口”约束下的“通过表示操作资源”子约束?您应该在哪里发回您想要查看的资源的表示,而不是发送有关如何操作它的说明。 @dayuloli 如果您仍在考虑解决方案,我有added an answer 我决定采用的方案,它可能会或可能会满足您的需求。 您可以使用 JSON 代替新的内容类型。例如:"$inc": "view": 1 虽然它在实践中可能是语法正确的选择,但我会正确地使用 PUT 或 POST,因为 PATCH 很少使用它可能会导致只需要 GET、PUT、POST 和 DELETE 的软件出现问题。 【参考方案2】:另一种方法可能是向系统添加另一个资源来跟踪个人资料的查看情况。你可以称之为“观看”。
查看个人资料的所有查看:
GET /profiles/123/viewings
要将查看添加到个人资料:
POST /profiles/123/viewings #在这里,您将使用请求正文中的自定义媒体类型提交详细信息。
要更新现有的查看:
PUT /viewings/815 # 使用您创建的自定义媒体类型在请求正文中提交修改后的查看属性。
要深入了解查看的详细信息:
GET /viewings/815
要删除查看:
删除 /viewings/815
另外,由于您要求最佳实践,请确保您的 RESTful 系统是 hypertext-driven。
在大多数情况下,在 URI 中使用查询参数没有任何问题 - 只是不要让您的客户认为他们可以操纵它们。
相反,创建一个体现参数试图建模的概念的媒体类型。给这个媒体类型一个简洁、明确和描述性的名称。然后记录此媒体类型。在 REST 中暴露查询参数的真正问题是这种做法经常导致带外通信,因此增加了客户端和服务器之间的耦合。
然后给你的系统一个统一的界面。例如,添加新资源始终是 POST。更新资源始终是 PUT。删除就是DELETE,getiing就是GET。
关于 REST 最难的部分是了解媒体类型如何影响系统设计(这也是菲尔丁在他的论文中因为时间用完而遗漏的部分)。如果您想要一个使用和记录媒体类型的超文本驱动系统的具体示例,请参阅Sun Cloud API。
【讨论】:
澄清一下:/viewings/815 指的是个人资料的第 815 次查看,是吗?【参考方案3】:在评估了之前的答案后,我认为 PATCH 是不合适的,并且出于我的目的,为了一项琐碎的任务而摆弄 Content-Type 违反了KISS principle。我只需要增加 n+1 所以我就这样做了:
PUT /profiles/123$views
++
其中++
是消息正文,控制器将其解释为将资源加一的指令。
我选择$
来区分资源的字段/属性,因为它是legal sub-delimiter,并且就我的目的而言,它似乎比/
更直观,在我看来,它具有可遍历性。
【讨论】:
【参考方案4】:我认为 Yanic 和 Rich 的两种方法都很有趣。 PATCH 不需要是安全的或非幂等的,但可以是为了更健壮地对抗并发。 Rich 的解决方案当然更容易在“标准”REST API 中使用。
见RFC5789:
按照 [RFC2616] 的定义,PATCH 既不安全也不幂等 9.1.
可以以幂等的方式发出 PATCH 请求, 这也有助于防止两者之间的碰撞产生不良后果 在相似的时间范围内对同一资源的 PATCH 请求。 来自多个 PATCH 请求的冲突可能比 PUT 冲突,因为某些补丁格式需要从 已知基点,否则它们会破坏资源。
【讨论】:
以上是关于以 RESTful 方式递增资源计数器:PUT 与 POST的主要内容,如果未能解决你的问题,请参考以下文章