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

Why PATCH is neither safe nor idempotent?

我很难理解为什么 PATCH 在 PUT 所在的地方不安全。 Aso 幂等部分 - 如果我更新资源的一个字段,更新后该字段 return 不是相同的值吗?

首先,PUT也不安全。

Safe methods are HTTP methods that do not modify resources. For instance, using GET or HEAD on a resource URL, should NEVER change the resource.

由于 PUT 请求(就此而言 PATCH 也是如此)更新了资源,所以它不能被缓存,因此它是不安全的。

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

An idempotent HTTP method is a HTTP method that can be called many times without different outcomes. It would not matter if the method is called only once, or ten times over. The result should be the same. Again, this only applies to the result, not the resource itself. This still can be manipulated (like an update-timestamp, provided this information is not shared in the (current) resource representation.

PUT 请求是幂等的背后的想法是,如果对资源的更新调用失败,客户端可以再次进行相同的调用而不会导致任何不良或不一致的状态。 PUT 请求之前应始终先有对资源的 GET 请求,并且如果且只有资源自此之后未更改,则应该成功。详细说明:- 通过类似的答案之一 -

现在 PATCH 请求仅用于更新选择性字段,预计不会 GET 资源表示。因此,对 PATCH 请求的多次调用可能会导致资源状态发生不良变化。因此它不是 IDEMPOTENT.

例如:- 有资源 Person

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

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

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

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

它可以通过使用 etags 或 If Modified Since headers 来实现幂等。

PATCH 更改了资源属性。更改可能需要属性的具体先前值,这使其成为非幂等的。

From Name=John to Name=Gargantua. 

重复应用后名称将变为 Gargantua,补丁将失败,因为它要求名称在更改前为 "John"

"from Name=John"

这不是安全的,因为通常您不能在不更改资源的情况下安全地执行 PATCH 请求(这就是它的用途)。

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

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

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

对其他答案的评论:幂等性是通过连续多次重复操作来定义的。说某事不是幂等的,因为如果在两者之间或并行执行某些其他操作,效果会有所不同,这不是一个有效的论点(如果是这种情况,一般来说没有任何操作是幂等的)。从数学上讲,幂等变换是一种产生相同结果的变换,无论您应用它的频率如何(比如将某物旋转 360 度)。当然,如果您在两者之间应用任何其他操作,两次 360 度旋转可能会产生不同的结果。

我最近开始研究 Patch 是否是幂等的,在阅读了 JSON 补丁格式后,我了解到如果使用 Patch 方法应用添加操作,则请求完全有可能是非幂等的幂等的,因为如果多次发出相同的请求,它可以将新值添加到现有资源。

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