使用标头进行条件操作

本节展示了 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 注解)将此字段标记为版本标记。

当 Spring Data REST 将前一个示例中的 POJO 作为 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 中(或可能在其他地方用于原生移动应用程序),您可以通过不反复检索相同内容来减少数据和电池电量的消耗。您还可以避免与其他客户端冲突,而是在需要协调差异时收到警报。

通过这种方式,只需对您的客户端进行一些微调和一些实体级编辑,后端就会提供时间敏感的详细信息,您可以在构建对客户友好的客户端时利用这些详细信息。