帶有標頭的條件式操作

本節說明 Spring Data REST 如何使用標準 HTTP 標頭來增強效能、條件化操作,並為更複雜的前端做出貢獻。

ETagIf-MatchIf-None-Match 標頭

ETag 標頭提供了一種標記資源的方法。這可以防止客戶端互相覆寫,同時也可以減少不必要的呼叫。

考慮以下範例

範例 1. 具有版本號碼的 POJO
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 註解)將此欄位標記為版本標記。

當前述範例中的 POJO 由 Spring Data REST 作為 REST 資源提供時,會帶有一個 ETag 標頭,其值為版本欄位的值。

如果我們提供類似以下的 If-Match 標頭,我們可以有條件地 PUTPATCHDELETE 該資源

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 標頭提供了一種檢查資源自上次請求以來是否已更新的方法,這讓應用程式可以避免重新傳送相同的資料。考慮以下範例

範例 2. 網域類型中擷取的最後修改日期
@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 DateCalendar、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-MatchIf-None-Match 標頭結合使用,可讓您建構對消費者資料方案和行動裝置電池壽命更友善的前端。若要執行此操作

  1. 識別需要鎖定的實體,並新增版本屬性。

    HTML5 很好地支援 data-* 屬性,因此將版本儲存在 DOM 中(例如 data-etag 屬性)。

  2. 識別可從追蹤最新更新中受益的條目。在提取這些資源時,將 Last-Modified 值儲存在 DOM 中(可能是 data-last-modified)。

  3. 在提取資源時,也將 self URI 嵌入到您的 DOM 節點中(可能是 data-uridata-self),以便輕鬆返回資源。

  4. 調整 PUT/PATCH/DELETE 操作以使用 If-Match,並處理 HTTP 412 Precondition Failed 狀態碼。

  5. 調整 GET 操作以使用 If-None-MatchIf-Modified-Since,並處理 HTTP 304 Not Modified 狀態碼。

透過將 ETag 元素和 Last-Modified 值嵌入到您的 DOM 中(或者可能是原生行動應用程式的其他位置),您可以減少資料和電池電量的消耗,而無需重複檢索相同的內容。您也可以避免與其他客戶端衝突,而是在需要協調差異時收到警示。

透過這種方式,只需稍微調整您的前端和一些實體層級的編輯,後端即可提供時間敏感的詳細資訊,您可以在建構客戶友善的客戶端時利用這些資訊。