以RESTful方式递增资源计数器:PUT与POST的区别

29

我有一个拥有计数器的资源。为了举例说明,我们称其为资料档案,而计数器是该资料档案的浏览次数

根据REST维基的规定,PUT请求应该用于资源的创建或修改,并且应该具有幂等性。如果我要更新资料档案的名称,那么这种组合就很好,因为我可以发出一条将名称设置为某个值的PUT请求,重复1000次也不会改变结果。

对于这些标准的PUT请求,我让浏览器执行以下操作:

PUT /profiles/123?property=value&property2=value2

要增加计数器,可以这样调用URL:

PUT /profiles/123/?counter=views

每次调用都会导致计数器增加。从技术上讲,这是一种更新操作,但它违反了幂等性。

我正在寻求指导/最佳实践。你只是将其作为POST请求处理吗?

4个回答

15

我认为正确的答案是使用PATCH方法。我没有看到其他人推荐将其用于原子地增加计数器,但我相信RFC 2068清楚地表明了这一点:

PATCH方法类似于PUT方法,不同之处在于实体包含一个差异列表,该列表列出了请求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 = 500views - 1views + 3在语法上都是有效的(但在语义上可能被禁止)。

我能理解一些人对于再次定义一个新的媒体类型持反感态度,但我谦虚地建议这比使用POST/PUT更为正确。为一个字段而创建资源,包括它自己的URI和细节(我实际上并没有保留所有信息,只有整数值),对我来说听起来很麻烦且不正确。如果我要维护23个不同的计数器怎么办?


1
略微偏离标准,因为在应用PATCH操作后没有“资源的期望内容”。例如,实体是要执行的指令,而不是期望的结果。 - Pocketsand
继@Pocketsand所说的,这种方法是否不违反“统一接口”约束下的“通过表示操作资源”的子约束?您应该发送要查看的资源的表示,而不是发送有关如何操作它的指令。 - dayuloli
如果你还在考虑解决方案,我已经添加了一个答案,分享了我最终采用的方法,也许适合你的需求。@dayuloli - Pocketsand
你可以使用 JSON 替代新的内容类型。例如:{"$inc": "view": 1}。 - Alan Evangelista
虽然在语法上来说PATCH可能是正确的选择,但实际上我会选择PUT或POST,因为PATCH很少使用,这可能会导致只期望GET、PUT、POST和DELETE的软件出现问题。 - Gellweiler

10

另一种选择是向系统添加另一个资源来跟踪个人资料的浏览记录,可以称之为“Viewing”。

要查看个人资料的所有Viewings:

GET /profiles/123/viewings

要向个人资料添加Viewing:

POST /profiles/123/viewings #在请求正文中使用自定义媒体类型提交详细信息。

要更新现有的Viewing:

PUT /viewings/815 #使用创建的自定义媒体类型在请求正文中提交Viewing的修订属性。

要深入了解Viewing的详情:

GET /viewings/815

要删除Viewing:

DELETE /viewings/815

此外,因为您要求最佳实践,请确保您的RESTful系统是基于超文本的驱动的。

就大部分而言,在URI中使用查询参数并没有什么问题-只是不要让客户端认为他们可以操纵它们。

相反,创建一个体现参数所要模拟概念的媒体类型。赋予该媒体类型一个简洁、明确且描述性的名称,并记录该媒体类型。REST中暴露查询参数的真正问题在于该做法经常导致带外通信,因此增加了客户端和服务器之间的耦合。

然后给您的系统一个统一的接口。例如,添加新资源始终是POST。更新资源始终是PUT。删除是DELETE,获取是GET。

REST最难的部分是理解媒体类型如何成为系统设计的一部分(这也是Fielding在他的论文中遗漏的部分,因为他时间不够了)。如果要了解使用并记录媒体类型的基于超文本驱动的系统的特定示例,请参见Sun Cloud API


只是为了澄清:/viewings/815 是指查看某个个人资料的第815次浏览,对吗? - Andres Jaan Tack

0

我认为Yanic和Rich的两种方法都很有趣。PATCH请求不需要安全或幂等,但可以为了更好地抵御并发而具备这些特性。在“标准”REST API中,Rich的解决方案肯定更容易使用。

请参见RFC5789

根据[RFC2616]第9.1节的定义,PATCH既不安全也不幂等。

PATCH请求可以以幂等的方式发出,这也有助于防止在类似时间框架内对同一资源进行两个PATCH请求之间的冲突导致不良结果。与PUT冲突相比,来自多个PATCH请求的冲突可能更加危险,因为某些补丁格式需要从已知基点操作,否则它们将破坏资源。


0

在评估了之前的答案后,我决定PATCH不适用于我的目的,并且对于一个微不足道的任务来说,摆弄Content-Type是违反KISS原则的。我只需要将n+1递增,所以我就这样做了:

PUT /profiles/123$views
++

其中++是消息体,由控制器解释为将资源增加一的指令。

我选择使用$来分隔资源的字段/属性,因为它是一个合法的子分隔符,并且在我看来比/更直观,后者给人的感觉是可遍历性。


你为++定义了哪种内容类型? - Mauro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接