REST:使用PATCH进行部分更新 - mscharhag


在开始之前,让我们快速检查一下为什么部分更新有用的原因:

  • 简单性-如果客户端只想更新一个字段,则部分更新请求可能更易于实现。
  • 带宽-如果您的资源表示量很大,则部分更新可以减少所需的带宽量。
  • 更新丢失-使用PUT替换资源可能会导致更新丢失问题。尽管部分更新不能解决此问题,但它们可以帮助减少可能的冲突数量。

PATCH方法不属于原始HTTP RFC的一部分。后来通过RFC 5789添加了它。PATCH方法既不安全也不是幂等的。但是,PATCH通常以幂等方式使用。
PATCH请求可以包含一个或多个请求的资源更改。如果请求多个更改,则服务器必须确保自动应用所有更改。RFC说:
服务器必须原子地应用整个更改集,并且绝不提供([..])部分修改的表示形式。如果无法成功应用整个补丁文档,则服务器不得应用任何更改。
PATCH的请求主体非常灵活。RFC仅表示请求正文必须包含有关如何修改资源的说明:
使用PATCH [..],封闭的实体包含一组指令,这些指令描述了应如何修改当前驻留在原始服务器上的资源以产生新版本。 
这意味着我们不必为PATCH请求使用与可能用于PUT或GET请求的资源表示相同的资源。我们可以使用完全不同的Media-Type来描述资源更改。
PATCH可以以两种常见的方式使用,它们都有各自的优缺点。在下一部分中,我们将对它们进行研究。
 
使用标准资源表示形式发送更改(JSON合并Patch)
使用PATCH的最直观的方法是保留GET或PUT请求中使用的标准资源表示形式。但是,对于PATCH,我们仅包括应更改的字段。
假设我们有一个简单的产品资源。一个简单的GET请求的响应可能如下所示:
GET /products/123
{
    "name": "Cool Gadget",
   
"description": "It looks very cool",
   
"price": 4.50,
   
"dimension": {
       
"width": 1.3,
       
"height": 2.52,
       
"depth": 0.9
    }
   
"tags": ["cool", "cheap", "gadget"]
}

现在我们要提高价格,删除便宜的标签,并更新产品宽度。为此,我们可以使用以下PATCH请求:

PATCH / products / 123 

    “ price”:6.20,
    “ dimension”:{ 
        “ width”:1.35 
    } 
    “ tags”:[“ cool”,“ gadget”] 
}

请求中未包括的字段应保持不变。为了从标签数组中删除一个元素,我们必须包括所有剩余的数组元素。
PATCH的这种用法称为JSON合并修补程序,在RFC 7396中定义。您可以想到仅使用字段子集的PUT请求。这种修补方式通常使PATCH请求成为幂等。
 
JSON合并Patch和空值
您应该了解JSON Merge Patch的一个警告:处理空值。
假设我们要删除先前使用的产品资源的描述。PATCH请求如下所示:
PATCH /products/123
{
    "description": null
}

为了满足客户的意图,服务器必须区分以下情况:
  • description
    字段不是JSON文件的一部分。在这种情况下,说明应保持不变。
  • description
    字段是JSON文档的一部分,并且具有值零。在此,服务器应删除当前
    description

在使用将JSON文档映射到对象的JSON库时,请注意这种区别。在Java之类的强类型编程语言中,当映射到强类型对象时,两种情况都有可能产生相同的结果(两种情况中的description字段都可能为null)。
因此,在支持空值时,应确保可以同时处理两种情况。
 
使用单独的Patch格式
如前所述,可以将不同的媒体类型用于PATCH请求。
再次,我们要提高价格,删除便宜的标签,并更新产品宽度。完成此操作的另一种方法可能如下所示:
PATCH /products/123
{
    "$.price": {
       
"action": "replace",
       
"newValue": 6.20
    },
   
"$.dimension.width": {        
       
"action": "replace",
       
"newValue": 1.35
    },
   
"$.tags[?(@ == 'cheap')]": {
       
"action": "remove"
    }
}

在这里,我们使用JSONPath表达式来选择要更改的值。然后,对于每个选定的值,我们使用一个小的JSON对象来描述所需的操作。
要替换简单值,此格式非常冗长。但是,它也具有一些优点,尤其是在处理数组时。如示例所示,我们可以删除一个数组元素而不发送所有剩余的数组元素。在使用大型阵列时,这很有用。
 
JSON Patch
用于描述使用JSON进行更改的标准媒体类型是JSON Patch(在RFC 6902中进行了描述)。使用JSON Patch,我们的请求如下所示:

PATCH /products/123
Content-Type: application/json-patch+json

[
    { 
        "op": "replace"
       
"path": "/price"
       
"value": 6.20
    },
    {
       
"op": "replace",
       
"path": "/dimension/width",
       
"value": 1.35
    },
    {
       
"op": "remove"
       
"path": "/tags/1"
    }
]

这看起来与我们之前的解决方案相似。JSON Patch使用op元素来描述所需的操作。path元素包含一个JSON指针(又一RFC),以选择应当施加的变化的元件。
请注意,当前版本的JSON Patch不支持按值删除数组元素。相反,我们必须使用数组索引删除该元素。使用/ tags / 1,我们可以选择第二个数组元素。
在使用JSON Patch之前,您应该评估它是否满足您的需求,以及您是否对它的局限性感到满意。在GitHub存储库json-patch2的问题中,您可以找到有关JSON Patch可能修订的讨论。
如果您使用的是XML而不是JSON,则应该看一下XML Patch(RFC 5261),它的工作原理类似,但是使用的是XML。
 
Accept-Patch标头
用于HTTP PATCH的RFC还为HTTP OPTIONS请求定义了一个新的响应标头:Accept-Patch。使用Accept-Patch,服务器可以传达给定资源的PATCH操作支持哪些媒体类型。RFC说:
对于支持使用PATCH方法的任何资源,“Accept-Patch”都应该出现在OPTIONS响应中。
支持PATCH方法并使用JSON Patch的资源的示例HTTP OPTIONS请求/响应可能如下所示:

OPTIONS /products/123
HTTP/1.1 200 OK
Allow: GET, PUT, POST, OPTIONS, HEAD, DELETE, PATCH
Accept-Patch: application/json-patch+json

 
对HTTP PATCH操作的响应
PATCH RFC并不要求PATCH操作的响应主体看起来如何。可以返回更新的资源。也可以将响应主体留空。
服务器通常使用以下HTTP状态代码之一响应HTTP PATCH请求:
  • 204(无内容)-表示操作已成功完成,没有数据返回
  • 200(Ok)-操作已成功完成,并且响应主体包含更多信息(例如,更新的资源)。
  • 400(错误请求)-请求正文格式错误,无法处理。
  • 409(冲突)-请求在语法上有效,但不能应用于资源。例如,如果由JSON指针(路径字段)选择的元素不存在,则可以将其与JSON补丁一起使用。

 
总结
PATCH操作非常灵活,可以以不同的方式使用。JSON Merge Patch(JSON合并Patch)使用标准资源表示来执行部分更新。但是,JSON Patch 使用单独的PATCH格式来描述所需的更改。还可以提出自定义的PATCH格式。支持PATCH操作的资源应返回OPTIONS请求的Accept-Patch标头。