Conditional Operations with Headers

本节显示 Spring Data REST 如何使用标准 HTTP 头部来增强性能、条件化操作以及促成更为完善的前端。

ETag, If-Match, and If-None-Match Headers

ETag header 提供了一种标记资源的方法。这可以防止客户端相互覆盖,同时也可能减少不必要的调用。

请考虑以下示例:

Example 1. A POJO with a version number
Unresolved include directive in modules/ROOT/pages/etags-and-other-conditionals.adoc - include::example$support/ETagUnitTests.java[]
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 消息以进行发送回。然后,该客户可以按需要进行追赶。

术语"`version,`"对于不同的数据存储可能具有不同的语义,甚至可能在你的应用程序中具有不同的语义。Spring Data REST 实际上委托给数据存储的元模型,以识别字段是否具有版本,如果具有,则仅在`ETag`元素匹配时允许列出的更新。

If-None-Match header 提供了一个替代。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 header

If-Modified-Since header 提供了一种方法来检查自上次请求以来是否已经更新了资源,这可以让应用程序避免重新发送相同的数据。考虑以下示例:

Example 2. The last modification date captured in a domain type
Unresolved include directive in modules/ROOT/pages/etags-and-other-conditionals.adoc - include::example$mongodb/Receipt.java[]
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 和参数时使用标头值。

Architecting a More Efficient Front End

ETag 元素与 If-MatchIf-None-Match 标头组合使用,使您可以构建一个更友好的前端,以供用户使用其数据计划和移动端电池续航。要做到这一点:

  1. 识别需要锁定的实体并添加版本属性。HTML5 很好的支持 data-* 属性,因此将版本存储在 DOM 中(例如此处所示的 data-etag 属性中)。

  2. 识别可以从跟踪最新更新中受益的条目。在获取这些资源时,将`Last-Modified`值存储在DOM中(可能为`data-last-modified`)。

  3. 在获取资源时,也可以在你的DOM节点(可能是`data-uri`或`data-self`)内嵌入`self`URI,以便轻松返回资源。

  4. 调整`PUT`/PATCH/DELETE`操作以使用`If-Match,并且还处理HTTP `412 Precondition Failed`状态码。

  5. 调整`GET`操作以使用`If-None-Match`和`If-Modified-Since`,并且处理HTTP `304 Not Modified`状态码。

在你的 DOM (或对于原生移动应用而言可能在其他位置)中嵌入 ETag 元素和 Last-Modified 值,你可以通过不再重复检索相同内容来减少数据和电池能耗。你还可以避免与其他客户端冲突,相反,在你需要调和差异时收到警告。

通过这种方式,只需在你的前端进行一点调整和一些实体级别的编辑,后端就会提供你在为面向客户的客户端构建时可以兑现的时间敏感详情。