為什麼 HTTP PATCH 方法不是冪等的及其延伸

greenlihui發表於2019-04-06

冪等性

首先來看什麼是冪等性,根據 rfc2616(Hypertext Transfer Protocol -- HTTP/1.1) 文件第 50 頁底部對 Idempotent Methods 的定義:

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

翻譯過來也就是:相同的請求執行多次和執行一次的副作用是一樣的

段落接下來就給出了具有冪等性的方法:

The methods GET, HEAD, PUT and DELETE share this property. Also, the methods OPTIONS and TRACE SHOULD NOT have side effects, and so are inherently idempotent.

可以看出,GETHEADPUTDELETEOPTIONSTRACE 方法都是冪等的。

PUT 和 PATCH

根據約定( Convention ),PUT 方法用於更新資料,PATCH 方法也用於更新資料,為什麼 PUT 方法是冪等的而 PATCH 方法不是冪等的呢?我們繼續研究文件(第54頁):

The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.

PUT 方法將請求所包含的實體儲存在所提供的 Request-URI 下。如果該 URI 指代一個已經存在的資源,那麼請求中的實體應該被視為儲存在原伺服器上的實體的修改版本。如果 Request-URI 沒有指向一個現有資源,並且該 URI 可以被髮送請求的使用者代理定義為新資源,則原伺服器可以使用該 URI 來建立資源。

這裡說的很明白了,PUT 用做更新操作的時候是提交一整個更新後的實體,而不是需要修改的實體中的部分屬性。當 URI 指向一個存在的資源,伺服器要做的事就是查詢並替換。

接下來看 PATCH(PATCH 方法在原文件中沒有找到相關描述,後來發現在另一個 RFC 裡面 - RFC5789):

The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a "patch document" identified by a media type. If the Request-URI does not point to an existing resource, the server MAY create a new resource, depending on the patch document type (whether it can logically modify a null resource) and permissions, etc.

PATCH 方法請求將一組描述在請求實體裡的更改應用到 Request-URI 標誌的資源。這組更改以稱為 "補丁文件" 的格式(該格式由媒體型別標誌)表示,如果 Request-URI 未指向現有資源,伺服器可能根據補丁文件的型別(是否可以在邏輯上修改空資源)和許可權等來建立一個新資源。

所以可以知道 PATCH 請求中的實體是一組將要應用到實體的更改,而不是像 PUT 請求那樣是要替換舊資源的實體,但是這並沒有解決 PATCH 方法為什麼不是冪等的問題。不著急,繼續讀,接下來就給出了 PUT 和 PATCH 的區別:

The difference between the PUT and PATCH requests is reflected in the way the server processes the enclosed entity to modify the resource identified by the Request-URI. In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version. The PATCH method affects the resource identified by the Request-URI, and it also MAY have side effects on other resources; i.e., new resources may be created, or existing ones modified, by the application of a PATCH.

PUT 和 PATCH 請求的區別體現在伺服器處理封閉實體以修改 Request-URI 標誌的資源的方式。在一個 PUT 請求中,封閉實體被認為是儲存在源伺服器上的資源的修改版本,並且客戶端正在請求替換儲存的版本。而對於 PATCH 請求,封閉實體中包含了一組描述當前保留在源伺服器上的資源應該如何被修改來產生一個新版本的指令。PATCH 方法影響由 Request-URI 標誌的資源,而且它也可能對其他資源有副作用;也就是,通過使用 PATCH,新資源可能被創造,或者現有資源被修改。

以上就是答案。可以理解為,PATCH 請求中的實體儲存的是修改資源的指令,該指令指導伺服器來對資源做出修改,所以不是冪等的。
可能有點抽象,打個比方:對於存在伺服器中的 A 物件有個屬性 B 為 1,如果要修改 B 屬性為 3,則 PUT 請求是直接將修改過 B 屬性的整個新物件傳送給伺服器查詢並替換。而 PATCH 請求是在實體中包含指令 --- 將 A 物件中 B 屬性的值加 2,那麼如果該請求被執行多次的話,B 屬性就可能不為 3 了,而 PUT 請求不論執行多少次,B 屬性永遠都是 3,所以說 PUT 方法是冪等的,而 PATCH 方法不是冪等的。

PUT 和 POST

在看請求相關的帖子的時候,偶爾也會看見爭論說使用 PUT 來新增資源,使用 POST 來修改資源,或者說這兩個方法差別不大,沒必要這麼明確分工。上文也提到了 PUT 方法的 URI 指向的資源不存在的時候也可以建立新資源。那到底怎麼用,都寫到這裡了繼續是用文件來說話,有關 POST 方法的說明:

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:

  • Annotation of existing resources;
  • Posting a message to a bulletin board, newsgroup, mailing list or similar group of articles;
  • Providing a block of data, such as the result of submitting a form, to a data-handling process;
  • Extending a database through an append operation.

The actual function performed by the POST method is determined by the server and is usually dependent on the Request-URI. The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate to a database.

POST 方法用於請求源伺服器接受請求中的實體作為 Request-URI 所標誌的資源的新下級。 POST 方法旨在允許一個統一的方法來涵蓋以下功能:

  • 現有資源的註釋;
  • 在公告欄,新聞組,郵件列表或類似文章組中釋出訊息;
  • 提供資料塊,例如提交表單的結果,資料處理過程;
  • 通過追加操作擴充套件資料庫。

POST方法執行的實際功能由伺服器確定,通常依賴於 Request-URI。 釋出的實體從屬於該 URI,其方式與檔案從屬於包含它的目錄相同,新聞文章從屬於釋出它的新聞組,或者記錄從屬於資料庫。

加黑的第一句話是不是很熟悉,用 RESTful API 實現前後端互動介面的朋友看到這裡應該就清楚了。這也是為什麼 POST /api/articles 在 RESTful 中被建議用來建立文章而不是更新文章的原因。此外,POST 請求不是冪等的,以為著如果把它用來當作資源更新操作,會創造多個相同的資源,這是更新操作不希望產生的副作用,所以還是用 POST 新增資源,PUT 更新資源吧。

當然,這些都是約定( convertion ) 而不是規定( standard ),如果你就是喜歡用 PUT 新建資源,POST 來修改資源,那我只能說對不起讓你花這麼長時間看篇文章了,僅僅使用 GET 和 POST 完成所有操作也還大有人在??。

相關文章