帶標頭的條件操作
本節展示了 Spring Data REST 如何使用標準 HTTP 標頭來提高效能、條件化操作並促進更復雜的前端。
ETag、If-Match 和 If-None-Match 標頭
ETag 標頭提供了一種標記資源的方式。這可以防止客戶端相互覆蓋,同時也可以減少不必要的呼叫。
考慮以下示例
class Sample {
@Version Long version; (1)
Sample(Long version) {
this.version = version;
}
}
| 1 | @Version 註解(如果您使用的是 Spring Data JPA,則為 JPA 中的註解;對於所有其他模組,則為 Spring Data 的 org.springframework.data.annotation.Version 註解)將此欄位標記為版本標記。 |
在前面的示例中,當 Spring Data REST 將 POJO 作為 REST 資源提供時,它具有一個 ETag 標頭,其值為版本欄位的值。
如果提供以下 If-Match 標頭,我們可以有條件地 PUT、PATCH 或 DELETE 該資源
curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...
只有當資源的當前 ETag 狀態與 If-Match 標頭匹配時,操作才會執行。此保護措施可防止客戶端相互覆蓋。兩個不同的客戶端可以獲取資源並具有相同的 ETag。如果一個客戶端更新資源,它會在響應中獲得一個新的 ETag。但第一個客戶端仍然擁有舊標頭。如果該客戶端嘗試使用 If-Match 標頭進行更新,則更新會失敗,因為它們不再匹配。相反,該客戶端會收到一個 HTTP 412 Precondition Failed 訊息。客戶端然後可以根據需要進行追趕。
“版本”一詞在不同的資料儲存中,甚至在您的應用程式中,可能具有不同的語義。Spring Data REST 有效地委託給資料儲存的元模型,以識別字段是否已版本化,如果是,則僅在 ETag 元素匹配時才允許列出的更新。 |
If-None-Match 標頭提供了另一種選擇。If-None-Match 不用於條件更新,而是允許條件查詢。請考慮以下示例
curl -v -H 'If-None-Match: <value of previous etag>' ...
前面的命令(預設情況下)執行一個 GET。Spring Data REST 在執行 GET 時檢查 If-None-Match 標頭。如果標頭與 ETag 匹配,它會得出結論,即沒有任何更改,並且不傳送資源的副本,而是返回 HTTP 304 Not Modified 狀態碼。從語義上講,它的含義是“如果提供的標頭值與伺服器端版本不匹配,則傳送整個資源。否則,不傳送任何內容。”
此 POJO 來自基於 ETag 的單元測試,因此它沒有 @Entity (JPA) 或 @Document (MongoDB) 註解,這在應用程式程式碼中是預期的。它只關注帶有 @Version 的欄位如何生成 ETag 標頭。 |
If-Modified-Since 標頭
If-Modified-Since 標頭提供了一種檢查資源自上次請求以來是否已更新的方法,這使應用程式可以避免重新發送相同的資料。請考慮以下示例
@Document
public class Receipt {
public @Id String id;
public @Version Long version;
public @LastModifiedDate Date date; (1)
public String saleItem;
public BigDecimal amount;
}
| 1 | Spring Data Commons 的 @LastModifiedDate 註解允許以多種格式捕獲此資訊(JodaTime 的 DateTime、舊版 Java Date 和 Calendar、JDK8 日期/時間型別以及 long/Long)。 |
使用前面示例中的日期欄位,Spring Data REST 返回一個類似於以下的 Last-Modified 標頭
Last-Modified: Wed, 24 Jun 2015 20:28:15 GMT
此值可以捕獲並用於後續查詢,以避免在資料未更新時重複獲取相同資料,如以下示例所示
curl -H "If-Modified-Since: Wed, 24 Jun 2015 20:28:15 GMT" ...
使用上述命令,您要求僅當資源自指定時間以來發生更改時才獲取。如果是這樣,您將獲得一個修訂後的 Last-Modified 標頭,用於更新客戶端。如果不是,您將收到一個 HTTP 304 Not Modified 狀態碼。
標頭格式完美,可以傳送回以進行未來的查詢。
| 請勿將標頭值與不同的查詢混淆。結果可能會非常糟糕。僅當您請求完全相同的 URI 和引數時才使用標頭值。 |
構建更高效的前端
ETag 元素與 If-Match 和 If-None-Match 標頭相結合,可讓您構建一個對使用者的資料計劃和移動裝置電池續航更友好的前端。為此,請執行以下操作
-
識別需要鎖定的實體並新增版本屬性。
HTML5 很好地支援
data-*屬性,因此將版本儲存在 DOM 中(例如data-etag屬性)。 -
識別那些從跟蹤最新更新中受益的條目。在獲取這些資源時,將
Last-Modified值儲存在 DOM 中(可能是data-last-modified)。 -
在獲取資源時,還在 DOM 節點中嵌入
selfURI(可能是data-uri或data-self),以便輕鬆返回資源。 -
調整
PUT/PATCH/DELETE操作以使用If-Match,並處理 HTTP412 Precondition Failed狀態碼。 -
調整
GET操作以使用If-None-Match和If-Modified-Since,並處理 HTTP304 Not Modified狀態碼。
透過在 DOM 中嵌入 ETag 元素和 Last-Modified 值(或者對於原生移動應用程式可能在其他地方),您可以減少資料和電池電量的消耗,因為您不會一遍又一遍地檢索相同的內容。您還可以避免與其他客戶端發生衝突,而是在需要協調差異時收到警報。
以這種方式,只需對前端進行少量調整和一些實體級編輯,後端即可提供時間敏感的詳細資訊,您可以在構建客戶友好的客戶端時利用這些詳細資訊。