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

banq發表於2021-03-06

在開始之前,讓我們快速檢查一下為什麼部分更新有用的原因:
  • 簡單性-如果客戶端只想更新一個欄位,則部分更新請求可能更易於實現。
  • 頻寬-如果您的資源表示量很大,則部分更新可以減少所需的頻寬量。
  • 更新丟失-使用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

<p class="indent">[
    { 
        "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標頭。


 

相關文章