Conditional Operations with Headers

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

This section shows how Spring Data REST uses standard HTTP headers to enhance performance, conditionalize operations, and contribute to a more sophisticated frontend.

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

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

The ETag header provides a way to tag resources. This can prevent clients from overriding each other while also making it possible to reduce unnecessary calls.

请考虑以下示例:

Consider the following example:

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)将此字段标记为版本标记。
2 The @Version annotation (the JPA one in case you’re using Spring Data JPA, the Spring Data org.springframework.data.annotation.Version one for all other modules) flags this field as a version marker.

前面对例中的 POJO 通过 Spring Data REST 作为 REST 资源提供时,具有带有版本字段值的 ETag 标头。

The POJO in the preceding example, when served up as a REST resource by Spring Data REST, has an ETag header with the value of the version field.

如果我们提供以下 If-Match 标头,我们可以有条件地 PUTPATCHDELETE 该资源:

We can conditionally PUT, PATCH, or DELETE that resource if we supply a If-Match header such as the following:

curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...

仅当资源的当前 ETag 状态与 If-Match 标头匹配时,才会执行该操作。此保护措施可防止客户相互影响。两个不同的客户可以提取资源并拥有相同的 ETag。如果一个客户更新资源,它会在响应中获取一个新的 ETag。但是第一个客户仍然拥有旧标头。如果该客户尝试使用 If-Match 标头进行更新,则更新会失败,因为它们不再匹配。相反,该客户会收到一个 HTTP 412 Precondition Failed 消息以进行发送回。然后,该客户可以按需要进行追赶。

Only if the resource’s current ETag state matches the If-Match header is the operation carried out. This safeguard prevents clients from stomping on each other. Two different clients can fetch the resource and have an identical ETag. If one client updates the resource, it gets a new ETag in the response. But the first client still has the old header. If that client attempts an update with the If-Match header, the update fails because they no longer match. Instead, that client receives an HTTP 412 Precondition Failed message to be sent back. The client can then catch up however is necessary.

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

The term, “version,” may carry different semantics with different data stores and even different semantics within your application. Spring Data REST effectively delegates to the data store’s metamodel to discern if a field is versioned and, if so, only allows the listed updates if ETag elements match.

If-None-Match header 提供了一个替代。If-None-Match 允许条件查询,而不是条件更新。考虑以下示例:

The If-None-Match header provides an alternative. Instead of conditional updates, If-None-Match allows conditional queries. Consider the following example:

curl -v -H 'If-None-Match: <value of previous etag>' ...

前面的命令(默认情况下)运行 GET。Spring Data REST 会在进行 GET 时检查 If-None-Match 标头。如果标头与 ETag 匹配,它会得出以下结论:没有任何更改,并且返回 HTTP 304 Not Modified 状态代码,而不是发送资源副本。在语义上,它读作“如果提供的标头值与服务器端版本不匹配,则发送整个资源。否则,不发送任何内容。

The preceding command (by default) runs a GET. Spring Data REST checks for If-None-Match headers while doing a GET. If the header matches the ETag, it concludes that nothing has changed and, instead of sending a copy of the resource, sends back an HTTP 304 Not Modified status code. Semantically, it reads “If this supplied header value does not match the server-side version, send the whole resource. Otherwise, do not send anything.”

此 POJO 来自基于`ETag`的单元测试,因此它没有在应用程序代码中预期的`@Entity`(JPA)或`@Document`(MongoDB)注解。它仅关注字段如何通过`@Version`导致`ETag`标头。

This POJO is from an ETag-based unit test, so it does not have @Entity (JPA) or @Document (MongoDB) annotations, as expected in application code. It focuses solely on how a field with @Version results in an ETag header.

If-Modified-Since header

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

The If-Modified-Since header provides a way to check whether a resource has been updated since the last request, which lets applications avoid resending the same data. Consider the following example:

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)。
2 Spring Data Commons’s @LastModifiedDate annotation allows capturing this information in multiple formats (JodaTime’s DateTime, legacy Java Date and Calendar, JDK8 date/time types, and long/Long).

通过前面对例中的日期字段,Spring Data REST 返回类似于以下内容的 Last-Modified 标头:

With the date field in the preceding example, Spring Data REST returns a Last-Modified header similar to the following:

Last-Modified: Wed, 24 Jun 2015 20:28:15 GMT

此值可以被捕获并用于后续查询,以避免在未更新时两次提取相同数据,如下例所示:

This value can be captured and used for subsequent queries to avoid fetching the same data twice when it has not been updated, as the following example shows:

curl -H "If-Modified-Since: Wed, 24 Jun 2015 20:28:15 GMT" ...

通过前面的命令,您要求仅当资源自指定时间以来已更改时才提取资源。如果是,您将获得经过修改的 Last-Modified 标头,以用于更新客户。如果不是,您会收到一个 HTTP 304 Not Modified 状态代码。

With the preceding command, you are asking that a resource be fetched only if it has changed since the specified time. If so, you get a revised Last-Modified header with which to update the client. If not, you receive an HTTP 304 Not Modified status code.

该标头经过完美格式化,可以发送回以进行将来查询。

The header is perfectly formatted to send back for a future query.

不要将标头值与不同的查询混合匹配。结果可能是灾难性的。仅在请求完全相同的 URI 和参数时使用标头值。

Do not mix and match header value with different queries. Results could be disastrous. Use the header values ONLY when you request the exact same URI and parameters.

Architecting a More Efficient Front End

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

ETag elements, combined with the If-Match and If-None-Match headers, let you build a front end that is more friendly to consumers' data plans and mobile battery lives. To do so:

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

HTML5 nicely supports data-* attributes, so store the version in the DOM (somewhere such as an data-etag attribute).

  1. Identify the entities that need locking and add a version attribute.[.iokays-translated-e8c51b811b0f78240f2dcfaaebeb0dd9] HTML5 很好的支持 data-* 属性,因此将版本存储在 DOM 中(例如此处所示的 data-etag 属性中)。

HTML5 nicely supports data-* attributes, so store the version in the DOM (somewhere such as an data-etag attribute).

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

  2. Identify the entries that would benefit from tracking the most recent updates. When fetching these resources, store the Last-Modified value in the DOM (data-last-modified perhaps).

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

  4. When fetching resources, also embed self URIs in your DOM nodes (perhaps data-uri or data-self) so that it is easy to go back to the resource.

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

  6. Adjust PUT/PATCH/DELETE operations to use If-Match and also handle HTTP 412 Precondition Failed status codes.

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

  8. Adjust GET operations to use If-None-Match and If-Modified-Since and handle HTTP 304 Not Modified status codes.

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

By embedding ETag elements and Last-Modified values in your DOM (or perhaps elsewhere for a native mobile app), you can then reduce the consumption of data and battery power by not retrieving the same thing over and over. You can also avoid colliding with other clients and, instead, be alerted when you need to reconcile differences.

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

In this fashion, with just a little tweaking on your front end and some entity-level edits, the backend serves up time-sensitive details you can cash in on when building a customer-friendly client.