如何使POST請求具有冪等性防止重複提交 - mscharhag

banq發表於2021-06-22

冪等性是一個積極的 API 特性。它有助於使 API 更具容錯性,因為客戶端可以在出現連線問題時安全地重試請求。
HTTP 規範將 GET、HEAD、OPTIONS、TRACE、PUT 和 DELETE 方法定義為冪等的。這些方法中的 GET、PUT 和 DELETE 是 REST API 中通常使用的方法。以冪等方式實現 GET、PUT 和 DELETE 通常不是什麼大問題。
POST 和 PATCH 有點不同,它們都沒有被指定為冪等的。但是,兩者都可以在冪等性方面實現,從而使客戶在出現問題時更容易。在這篇文章中,我們將探討使 POST 和 PATCH 請求冪等的不同選項。
 

使用唯一的業務約束
在建立新資源(通常透過 POST 表示)時提供冪等性的最簡單方法是獨特的業務約束。
例如,假設我們要建立一個需要唯一電子郵件地址的使用者資源:

POST /users

{
    "name": "John Doe",
    "email": "john@doe.com"
}

如果客戶端不小心傳送了兩次此請求,則第二個請求將返回錯誤,因為具有給定電子郵件地址的使用者已存在。在這種情況下,通常會返回 HTTP 400(錯誤請求)或 HTTP 409(衝突)作為狀態程式碼。
請注意,用於提供冪等性的約束不必是請求正文的一部分。URI 部分和關係也有助於形成唯一約束。
一個很好的例子是與父資源以一對一關係關聯的資源。例如,假設我們要使用給定的訂單 ID 支付訂單。
付款請求可能如下所示:


POST /order/<order-id>/payment

{
    ... (payment details)
}

一個訂單隻能支付一次,因此/payment與其父資源/order/<order-id> 是一對一的關係。如果給定訂單已經存在付款,則伺服器可以拒絕任何進一步的付款嘗試。
 

使用 ETag
Etag是使更新請求冪等的好方法。ETag 由伺服器根據當前資源表示生成。ETag 在ETag標頭值中返回。例如:
請求:

GET /users/123

響應:

HTTP/1.1 200 Ok
ETag: "a915ecb02a9136f8cfc0c2c5b2129c4b"

{
    "name": "John Doe",
    "email": "john@doe.com"
}


現在假設我們要使用JSON Merge Patch請求來更新使用者名稱:

PATCH /users/123
If-Match: "a915ecb02a9136f8cfc0c2c5b2129c4b"

{
    "name": "John Smith"
}

我們使用If-Match條件告訴伺服器僅在 ETag 匹配時才執行請求。更新資源會導致伺服器端更新 ETag。因此,如果請求被意外傳送兩次,伺服器會拒絕第二次請求,因為 ETag 不再匹配。在這種情況下,通常應該返回 HTTP 412(前提條件失敗)。
顯然 ETags 只能在資源已經存在的情況下使用。所以這個解決方案不能用於在建立資源時確保冪等性。從好的方面來說,這是一種標準化且易於理解的方式。
 

使用單獨的冪等鍵
另一種方法是使用單獨的客戶端生成的金鑰來提供冪等性。透過這種方式,客戶端生成一個金鑰並使用自定義標頭(例如Idempotency-Key)將其新增到請求中。
例如,建立新使用者的請求可能如下所示:

POST /users
Idempotency-Key: 1063ef6e-267b-48fc-b874-dcf1e861a49d

{
    "name": "John Doe",
    "email": "john@doe.com"
}

現在伺服器可以保留冪等金鑰Idempotency-Key並拒絕使用相同金鑰的任何進一步請求。
使用這種方法有兩個問題需要考慮:
  • 如何處理尚未成功完成的請求(例如透過返回 HTTP 4xx 或 5xx 狀態程式碼)?在這些情況下,伺服器是否應該儲存冪等金鑰?如果是這樣,客戶端如果想重試請求,總是需要使用新的冪等金鑰。
  • 如果伺服器使用已知冪等鍵檢索請求,返回什麼。

我個人傾向於僅在請求成功完成時才儲存冪等鍵。在第二種情況下,我將返回 HTTP 409(衝突)以指示已經執行了具有給定冪等鍵的請求。
但是,這裡的意見可能會有所不同。例如,Stripe API 使用 Idempotency-Key 標頭。Stripe 在所有情況下都儲存冪等鍵和返回的響應。如果提供的冪等鍵已經存在,則無需再次執行操作即可返回儲存的響應。
在我看來,後者可能會使客戶感到困惑。另一方面,它為客戶端提供了再次檢索先前執行的請求的響應的選項。
 

概括
一個簡單的唯一業務金鑰可用於為建立資源的操作提供冪等性。
對於非建立操作,我們可以將伺服器生成的 ETag 與If-Match標頭結合使用。這種方法具有標準化和廣為人知的優點。
作為替代方案,我們可以使用自定義請求標頭中提供的客戶端生成的冪等性金鑰。伺服器儲存那些冪等金鑰並拒絕包含已使用冪等金鑰的請求。這種方法可用於所有型別的請求。但是,它不是標準化的,有一些需要考慮的地方。

 

相關文章