为什么PATCH方法既不安全也不幂等?

61

我不太理解为什么PATCH方法不安全而PUT方法安全。还有幂等性部分 - 如果我更新资源的一个字段,那么更新后该字段不会返回相同的值吗?


1
相关链接:https://softwareengineering.stackexchange.com/questions/260818/why-patch-method-is-not-idempotent - Ciro Santilli OurBigBook.com
3
PUT不安全。OPTION、GET、HEAD是安全的。 - Jaydeep Shil
1
@Tony Vincent - 既然被采纳的答案得到了负面评价,想知道您是否可以更改被采纳的答案。这有助于访问者快速获取正确的信息。 - KGhatak
4个回答

94

由于一般情况下,执行 PATCH 请求时,无法安全地更改资源 (这就是它的作用),因此不是 安全 的。

那么相对于 PUT,为什么 PATCH 不是 幂等 的呢?这是因为应用更改的方式很重要。如果您想更改资源的 name 属性,则可以发送类似 {"name": "foo"} 的有效载荷,这确实是幂等的,因为执行此请求任意次数都会产生相同的结果:资源的 name 属性现在是 "foo"。

但是 PATCH 在如何更改资源方面具有更大的灵活性(请查看此处关于如何应用 JSON patch 的定义)。例如,它也可能意味着移动资源,并且可能看起来像这样:{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" }。这个操作显然不是幂等的,因为第二次调用会导致错误。

因此,虽然大多数 PATCH 操作可能是幂等的,但有些操作不是。

关于其他回答的一点说明:幂等性是通过多次重复操作来定义的。说某个操作不是幂等的,因为在其中执行了其他操作或并行操作导致效果不同,这不是一个有效的论据(如果是这种情况,通常没有任何操作会是幂等的)。从数学上讲,幂等变换是一种无论你如何多次应用都会产生相同结果的变换(就像将某物旋转360度)。当然,如果在两次360度旋转之间执行了其他操作,则可能会产生不同的结果。


89
我认为你陷入了和其他答案一样的误区:发送第二个带有你的 JSON 补丁的 PATCH 请求可能不会得到相同的响应,但是在第一个 PATCH 请求之后,资源的状态将与此相同,因此在这种情况下,你的 PATCH 请求具有幂等性。一个不具有幂等性的 PATCH 请求的例子是向数组追加项目:使用 JSON 补丁格式,{"op": "add", "path": "/-", "value": "foo"} 第一次将 [] 转换为 ["foo"],第二次将其转换为 ["foo", "foo"],第三次将其转换为 ["foo", "foo", "foo"] 等。 - Géry Ogam
这似乎是最准确的解释。因此,我猜作为惯例,如果我们相当确定操作是幂等的,那么可以使用Patch或Put。如果我们相当确定更新会导致非幂等性,则绝对使用patch,而永远不会使用put。现在谈到像差异更新或总更新之类的格式,我会说这取决于API设计人员采用一致的约定,一旦采用就不应更改。通常,对于任何类型的幂等更新,我将使用put,但一旦这些更新变得非幂等,就会使用patch。 - bharatj

9

最近我开始思考 Patch 是否具有幂等性,经过阅读 JSON patch 格式的相关内容,我了解到如果使用 Patch 方法进行添加操作,则请求可能是非幂等的,因为如果多次发出相同的请求,它可以向现有资源添加新值。

{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }


1
如果我们使用PUT方法进行添加操作,那么不会继续多次添加吗? - shikhar
看起来惯例是在不可幂等的情况下使用PATCH。 - Vivek Agrawal
1
为什么主体会有一个添加操作呢?这不是符合RESTful的规范,而是RPC。"添加"操作应该是对/a/b/c资源进行POST请求。 - Kevin Shaffer

8

PATCH修改资源的属性。更改可能需要属性的具体先前值,这使得它非幂等。

From Name=John to Name=Gargantua. 

经过多次更改,名称将变为Gargantua,补丁将失败,因为在更改之前需要将名称设为“John”。

"from Name=John"


1
你提到的条件变化,是指https://tools.ietf.org/html/rfc7232 中的条件请求还是其他什么?这些请求对于PUT也是可能的吗? - Ciro Santilli OurBigBook.com

-4

首先,PUT也不安全。

安全方法是指不修改资源的HTTP方法。例如,在资源URL上使用GET或HEAD,永远不应更改资源。

由于PUT请求(以及PATCH)更新了资源,因此无法缓存,因此不安全。

PUT请求虽然幂等,或者说PUT请求应该是幂等的。

幂等的HTTP方法是指可以重复调用多次而没有不同结果的HTTP方法。方法只调用一次或十次都没有关系,结果应该是相同的。同样,这仅适用于结果,而不是资源本身。这仍然可以被操作(例如更新时间戳,只要这些信息未在当前资源表示中共享即可)。

PUT请求被认为是幂等的,其背后的思想是,如果对资源的更新调用失败,客户端可以再次进行相同的调用,而不会导致任何不良或不一致的状态。PUT请求应始终在资源之前进行GET请求,并且仅当资源未更改时才应成功。详见:阅读类似答案之一-并发环境中的幂等PUT请求

现在,PATCH请求旨在仅更新选择性字段,不需要获取资源表示。因此,多次调用PATCH请求可能会导致资源状态的不良变化。因此它不是IDEMPOTENT

例如: 有一个资源Person

请求1: PATCH /person/1 {'age': 10} - 将资源的年龄更新为10

现在假设其他并行请求更改了资源的状态,比如

请求2: PATCH /person/1 {'age': 19} - 将资源的年龄更新为19

现在,如果再次发送请求1,则会将资源的年龄再次更新为10,从而导致不良结果。

但是可以使用ETags或If-Modified-Since头使其幂等。


3
如果用户再次发送请求1,那么为什么结果被认为是不可取的?我可以理解对于PUT操作的论点,因为你必须提供所有属性才能更改其中一个,但对于PATCH操作,你只限制在预期更新的属性上。 (另外,您是不是指“将资源年龄再次更新为10”?) - jt000
我已经按照你的建议修正了拼写错误,谢谢。从意义上讲,这是不可取的,因为两个请求都试图在过时的假设上更新资源。假设一个资源的初始值为7。由于该值是错误的,一个请求想要将7更新为10,另一个请求想要将7更新为11,而不是将10更新为11或反之亦然。 - hspandher
1
有趣的讨论话题。我可以看到 PUT 中,用户1更新年龄并尝试保留名称,而用户2则更新名称并尝试保留年龄。但对于 PATCH,他们明确地想要更新年龄。似乎这两个人只需要面对面交谈来决定正确的值 :) 但是,我听说过关于 PATCH 的一般性讨论,声称它不安全,因为某些实现需要预设状态(即 https://tools.ietf.org/html/rfc6902 - JSON PATCH 具有某些类似的不安全操作)。 - jt000
我想知道使用补丁的示例是否可以如下所示: (1)PATCH {"age":"10"} -> {"age":"10", "sex":"f","name":"a"}(2)PATCH {"name":"b"} -> {"age":"10", "sex":"f","name":"b"}(3)PATCH {"age":"10"} -> {"age":"10", "sex":"f","name":"b"}(1)和(3)请求相同,但返回不同的响应。 - fascynacja
12
这个论点是错误的。同样适用于DELETE,它是幂等的。但如果在DELETE之后资源被重新创建了呢?重要的是,DELETE和你所举的PATCH示例(仅将属性设置为新值)之后的结果是相同的。PATCH不是幂等的,因为它可能包含相对变化,这需要一个具体的资源状态来实现,详见我的答案。 - Skip
4
错误的逻辑:根据你的例子,PUT 也不是幂等的,因为之间的一次 PUT 请求也可能会更新资源。 - Géry Ogam

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