2013-04-09 77 views
4

我一直在尋找最佳實踐,以防止在使用POST創建新資源時意外創建重複資源,這是因爲資源將由服務器命名,因此PUT無法使用。我正在構建的API將被移動客戶端使用,我關心的情況是客戶端在提交POST請求後但在獲取響應之前斷開連接。我發現this question,但沒有提到使用條件POST,因此我的問題。REST:防止通過條件POST創建重複資源?

正在對父資源執行條件POST,類似於使用條件PUT修改資源,這個問題的合理解決方案?如果不是,爲什麼不呢?

客戶機/服務器交互將是一樣與條件PUT:

  1. 客戶端獲取父資源,包括ETag的反映其當前狀態(其中包括它的從屬資源),

  2. 客戶端執行有條件POST父資源(包括在If-Match頭父母的ETag值)來創建一個新的資源,

  3. 客戶端獲取得到之前斷開服務器響應,所以不知道它是否成功,

  4. 之後,重新連接時,客戶重新提交相同的條件POST請求,

  5. 無論是早先的要求,沒有達到服務器,因此服務器創建資源並用201回覆,或者更早的請求確實到達服務器,所以服務器回覆412並且不創建重複資源。

+0

如果另一個客戶端在兩者之間執行GET/POST對,這是否意味着GET/POST對將會失敗?這可能是高流量情況下的一個問題。 – geon 2013-05-02 15:49:48

+1

@geon您說得對,但所討論的集合資源是用戶特定的,而不是全局的,因此這種衝突的可能性很小。 – 2015-03-03 07:55:44

回答

0

我覺得這個方案可行。如果你想確保POST不會導致重複,你需要客戶端發送POST中唯一的東西。然後服務器可以驗證唯一性。

您不妨讓客戶端爲每個請求生成一個GUID,而不是通過GET從服務器獲取該GUID。

你的步驟則變爲: -

  1. 客戶端生成一個GUID
  2. 客戶端執行POST的資源,其中包括GUID
  3. 客戶端斷開連接,不知道它是否成功
  4. 客戶端再次連接並執行具有相同GUID的另一個POST
  5. 服務器檢查GUID並創建資源(如果它從未收到第一個POST)或指示這是一個dupl icate

使用PUT可能更安寧,並讓客戶端決定資源名稱。如果您不喜歡所選的名稱,則可能表明您已創建了該資源,但它的典型位置是服務器選擇的某個位置。

+0

缺點w /添加一個GUID字段是,你最終不得不圍繞一個額外的字段只是爲了重複檢測的目的,這是不可取的。最後我們確實使用了客戶端生成的GUID,所以我們可以使用PUT。 – 2015-03-03 08:00:41

+0

您是不是指客戶端生成的URI? – 2015-03-03 08:01:50

+0

是的,帶有GUID的URI。 – 2015-03-03 18:56:02

0

爲什麼不簡單地使用服務器選擇使用的任何內部機制在服務器上根據實際資源重複檢測。

這只是更安全的方式。

然後您將URL返回到適當的資源(無論是否新創建)。

如果父母ETag是基於子資源的狀態,那麼它不是檢查「重複資源」的可靠機制。你所知道的是,自上次以來,父母已經「改變」了。你甚至知道這是因爲你的舊POST在斷開連接後被處理了? ETag可能會改變任何東西。

這基本上是一個樂觀鎖定情景正在播放,並且歸結爲另一個問題。如果資源已經創建,那麼是什麼?這是一個錯誤?還是一個功能?你關心?當資源已經存在時,發送服務器默認忽略的創建請求會不好嗎?

如果它已經存在,但是「不同」足夠(即說名稱匹配但地址不同),是否是重複的?是更新嗎?這是嘗試更改現有資源的錯誤嗎?

另一個解決方案是做兩次旅行。一個來提出請求,另一個提交請求。如果請求中斷,則可以查詢請求的狀態。如果提交沒有完成,您可以再次提交。如果確實如此,你很高興,可以繼續前進。

只是取決於你的通信有多不穩定,以及這個特定的操作是多麼重要,你是否想要跳過安全環來做這件事。

+0

在我們的案例中,資源集合是一個不是集合的列表,因此合法地可以有相同資源的多個副本(儘管具有不同的ID)。我們想要防止的問題是客戶端代表請求單個副本的用戶意外創建多個副本。一般來說,父母的ETag是不可靠的,但是對於特定於用戶的父母資源來說,它是更可行的。最後,我們使用客戶端生成的GUID和PUT的簡單而強大的解決方案。 – 2015-03-03 08:08:04

+0

一個人們試圖維持REST純度的標準混亂的可愛例子。 – bbsimonbb 2016-02-16 10:32:24

1

您的解決方案很聰明,但並不理想。您的客戶可能永遠無法獲得他的201確認,並且必須將412錯誤解釋爲成功。

REST afficianados通常建議您使用空POST創建資源,然後,一旦客戶端擁有新創建的資源的ID,他就可以執行「冪等」更新來填充它。這很好,但是你可能需要使數據庫列可以爲空,否則就不會這樣,而且如果沒有其他人試圖同時更新,那麼你的更新只會是冪等的。

據我說,HTTP是片狀的。請求超時,瀏覽器窗口關閉,連接重新設置,列車與移動用戶一起進入隧道。有一個簡單,強大的模式來處理這個問題。不安全的操作應始終唯一標識,服務器應存儲並能夠在必要時重複對任何不安全請求的響應。這不是HTTP緩存,可以從緩存提供請求,但緩存可能因任何原因被刷新。這是服務器應用程序的保證,如果第二次看到「動作」請求,則存儲的響應將被重複而沒有其他任何事情發生。如果動作身份由服務器生成,那麼請求 - 響應應該專用於發送該ID。如果你爲一個不安全的請求實現這一點,那麼你可以爲所有人做這樣的事情,這樣做可以避免許多棘手的問題:連續的更新請求消除了其他用戶的更改或者達到了不兼容的狀態(「已經提交的訂單「),連續刪除請求產生404錯誤。

我有一個小小的谷歌文檔探索更充分的模式if you're interested

+0

有趣的建議,謝謝,但它會非常沉重的服務器實施,因爲他們必須保持所有行動和他們產生的反應歷史。此外,您提倡PUT對動作URI的不同語義(替代目標資源IFF在通過此動作URI之前未被替換),而不是「普通」PUT(總是替換目標資源),這是一個相當戲劇性的離開來自標準。正如你所說,它確實有一些好處,但我仍然贊成已經提到的其他更簡單的解決方案。 – 2016-02-17 06:30:10

+0

「非常」有點強!響應很小,只有幾千字節。如果你有很多卷,你可以使用一個ACID鍵值存儲(couchDB?)來存儲響應。我第一次使用這種模式的支付Web服務已經在SQL Server數據庫上快樂地離開了15年。開發,集成和支持非常簡單,我發現自己對這個問題的其他答案非常反感。你不會注意到:在所有關於你應該如何處理這個問題的RESTful討論中,沒有人會談論他們的經驗,他們的問題,他們的數量。 – bbsimonbb 2016-02-17 09:05:01

+0

也許,但是存儲資源所有修改的歷史可能需要比資源本身更多的存儲空間。從概念上講,存儲修改歷史是一個重大變化。爲了防止意外重複的資源創建,使用客戶端生成URI的PUT最簡單最簡單,然後如您所述,POST之後的空POST只需要額外存儲創建的ID直到它們被填充。 – 2016-02-17 20:10:54