parent
59bd21d2fc
commit
4a435c12f2
|
|
@ -1091,11 +1091,18 @@ configure the `ForwardedHeaderFilter` to remove and ignore such headers.
|
|||
[[filters-shallow-etag]]
|
||||
=== Shallow ETag
|
||||
|
||||
There is a `ShallowEtagHeaderFilter`. It is called shallow because it doesn't have any
|
||||
knowledge of the content. Instead it relies on buffering actual content written to the
|
||||
response and computing the ETag value at the end.
|
||||
The `ShallowEtagHeaderFilter` filter creates a "shallow" ETag by caching the content
|
||||
written to the response, and computing an MD5 hash from it. The next time a client sends,
|
||||
it does the same, but also compares the computed value against the `If-None-Match` request
|
||||
header and if the two are equal, it returns a 304 (NOT_MODIFIED).
|
||||
|
||||
See <<mvc-httpcaching-shallowetag>> for more details.
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed
|
||||
for each request. Other strategies at the controller level, described above, can avoid the
|
||||
computation. See <<mvc-caching>>.
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
|
||||
like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
|
||||
https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
|
||||
|
||||
|
||||
|
||||
|
|
@ -3702,46 +3709,33 @@ http://hdiv.org/[HDIV] is another web security framework that integrates with Sp
|
|||
[[mvc-caching]]
|
||||
== HTTP Caching
|
||||
|
||||
A good HTTP caching strategy can significantly improve the performance of a web application
|
||||
and the experience of its clients. The `'Cache-Control'` HTTP response header is mostly
|
||||
responsible for this, along with conditional headers such as `'Last-Modified'` and `'ETag'`.
|
||||
HTTP caching can significantly improve the performance of a web application. HTTP caching
|
||||
revolves around the "Cache-Control" response header and subsequently conditional request
|
||||
headers such as "Last-Modified" and "ETag". "Cache-Control" advises private (e.g. browser)
|
||||
and public (e.g. proxy) caches how to cache and re-use responses. An "ETag" header is used
|
||||
to make a conditional request that may result in a 304 (NOT_MODIFIED) without a body,
|
||||
if the content has not changed. "ETag" can be seen as a more sophisticated successor to
|
||||
the `Last-Modified` header.
|
||||
|
||||
The `'Cache-Control'` HTTP response header advises private caches (e.g. browsers) and
|
||||
public caches (e.g. proxies) on how they can cache HTTP responses for further reuse.
|
||||
|
||||
An http://en.wikipedia.org/wiki/HTTP_ETag[ETag] (entity tag) is an HTTP response header
|
||||
returned by an HTTP/1.1 compliant web server used to determine change in content at a
|
||||
given URL. It can be considered to be the more sophisticated successor to the
|
||||
`Last-Modified` header. When a server returns a representation with an ETag header, the
|
||||
client can use this header in subsequent GETs, in an `If-None-Match` header. If the
|
||||
content has not changed, the server returns `304: Not Modified`.
|
||||
|
||||
This section describes the different choices available to configure HTTP caching in a
|
||||
Spring Web MVC application.
|
||||
This section describes HTTP caching related options available in Spring Web MVC.
|
||||
|
||||
|
||||
|
||||
[[mvc-caching-cachecontrol]]
|
||||
=== Cache-Control
|
||||
=== `CacheControl`
|
||||
|
||||
Spring Web MVC supports many use cases and ways to configure "Cache-Control" headers for
|
||||
an application. While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234 Section 5.2.2]
|
||||
completely describes that header and its possible directives, there are several ways to
|
||||
address the most common cases.
|
||||
{api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for
|
||||
configuring settings related to the "Cache-Control" header and is accepted as an argument
|
||||
in a number of places:
|
||||
|
||||
Spring Web MVC uses a configuration convention in several of its APIs:
|
||||
`setCachePeriod(int seconds)`:
|
||||
|
||||
* A `-1` value won't generate a `'Cache-Control'` response header.
|
||||
* A `0` value will prevent caching using the `'Cache-Control: no-store'` directive.
|
||||
* An `n > 0` value will cache the given response for `n` seconds using the
|
||||
`'Cache-Control: max-age=n'` directive.
|
||||
|
||||
The {api-spring-framework}/http/CacheControl.html[`CacheControl`] builder
|
||||
class simply describes the available "Cache-Control" directives and makes it easier to
|
||||
build your own HTTP caching strategy. Once built, a `CacheControl` instance can then be
|
||||
accepted as an argument in several Spring Web MVC APIs.
|
||||
* {api-spring-framework}/web/servlet/mvc/WebContentInterceptor.html[`WebContentInterceptor`]
|
||||
* {api-spring-framework}/web/servlet/support/WebContentGenerator.html[`WebContentGenerator`]
|
||||
* <<mvc-caching-etag-lastmodified>>
|
||||
* <<mvc-caching-static-resources>>
|
||||
|
||||
While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all possible
|
||||
directives for the "Cache-Control" response header, the `CacheControl` type takes a
|
||||
use case oriented approach focusing on the common scenarios:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -3755,65 +3749,26 @@ accepted as an argument in several Spring Web MVC APIs.
|
|||
// Cache for ten days in public and private caches,
|
||||
// public caches should not transform the response
|
||||
// "Cache-Control: max-age=864000, public, no-transform"
|
||||
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
|
||||
.noTransform().cachePublic();
|
||||
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
|
||||
----
|
||||
|
||||
`WebContentGenerator` also accept a simpler `cachePeriod` property, in seconds, that
|
||||
works as follows:
|
||||
|
||||
|
||||
[[mvc-caching-static-resources]]
|
||||
=== Static resources
|
||||
|
||||
Static resources should be served with appropriate `'Cache-Control'` and conditional
|
||||
headers for optimal performance.
|
||||
<<mvc-config-static-resources,Configuring a `ResourceHttpRequestHandler`>> for serving
|
||||
static resources not only natively writes `'Last-Modified'` headers by reading a file's
|
||||
metadata, but also `'Cache-Control'` headers if properly configured.
|
||||
|
||||
You can set the `cachePeriod` attribute on a `ResourceHttpRequestHandler` or use
|
||||
a `CacheControl` instance, which supports more specific directives:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("/public-resources/")
|
||||
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
And in XML:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<mvc:resources mapping="/resources/**" location="/public-resources/">
|
||||
<mvc:cache-control max-age="3600" cache-public="true"/>
|
||||
</mvc:resources>
|
||||
----
|
||||
* A `-1` value won't generate a "Cache-Control" response header.
|
||||
* A `0` value will prevent caching using the `'Cache-Control: no-store'` directive.
|
||||
* An `n > 0` value will cache the given response for `n` seconds using the
|
||||
`'Cache-Control: max-age=n'` directive.
|
||||
|
||||
|
||||
|
||||
[[mvc-caching-etag-lastmodified]]
|
||||
=== @Controller caching
|
||||
=== Controllers
|
||||
|
||||
Controllers can support `'Cache-Control'`, `'ETag'`, and/or `'If-Modified-Since'` HTTP requests;
|
||||
this is indeed recommended if a `'Cache-Control'` header is to be set on the response.
|
||||
This involves calculating a lastModified `long` and/or an Etag value for a given request,
|
||||
comparing it against the `'If-Modified-Since'` request header value, and potentially returning
|
||||
a response with status code 304 (Not Modified).
|
||||
|
||||
As described in <<mvc-ann-httpentity>>, controllers can interact with the request/response using
|
||||
`HttpEntity` types. Controllers returning `ResponseEntity` can include HTTP caching information
|
||||
in responses like this:
|
||||
Controllers can add explicit support for HTTP caching. This is recommended since the
|
||||
lastModified or ETag value for a resource needs to be calculated before it can be compared
|
||||
against conditional request headers. A controller can add an ETag and "Cache-Control"
|
||||
settings to a `ResponseEntity`:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -3825,19 +3780,18 @@ in responses like this:
|
|||
String version = book.getVersion();
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
|
||||
.eTag(version) // lastModified is also available
|
||||
.body(book);
|
||||
.ok()
|
||||
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
|
||||
.eTag(version) // lastModified is also available
|
||||
.body(book);
|
||||
}
|
||||
----
|
||||
|
||||
Doing this will not only include `'ETag'` and `'Cache-Control'` headers in the response, it will **also convert the
|
||||
response to an `HTTP 304 Not Modified` response with an empty body** if the conditional headers sent by the client
|
||||
match the caching information set by the Controller.
|
||||
This will send an 304 (NOT_MODIFIED) response with an empty body, if the comparison
|
||||
to the conditional request headers indicates the content has not changed. Otherwise the
|
||||
"ETag" and "Cache-Control" headers will be added to the response.
|
||||
|
||||
An `@RequestMapping` method may also wish to support the same behavior.
|
||||
This can be achieved as follows:
|
||||
The check against conditional request headers can also be made in the controller:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
|
@ -3845,59 +3799,41 @@ This can be achieved as follows:
|
|||
@RequestMapping
|
||||
public String myHandleMethod(WebRequest webRequest, Model model) {
|
||||
|
||||
long lastModified = // 1. application-specific calculation
|
||||
long eTag = ... <1>
|
||||
|
||||
if (request.checkNotModified(lastModified)) {
|
||||
// 2. shortcut exit - no further processing necessary
|
||||
return null;
|
||||
if (request.checkNotModified(eTag)) {
|
||||
return null; <2>
|
||||
}
|
||||
|
||||
// 3. or otherwise further request processing, actually preparing content
|
||||
model.addAttribute(...);
|
||||
model.addAttribute(...); <3>
|
||||
return "myViewName";
|
||||
}
|
||||
----
|
||||
|
||||
There are two key elements here: calling `request.checkNotModified(lastModified)` and
|
||||
returning `null`. The former sets the appropriate response status and headers
|
||||
before it returns `true`.
|
||||
The latter, in combination with the former, causes Spring MVC to do no further
|
||||
processing of the request.
|
||||
<1> Application-specific calculation.
|
||||
<2> Response has been set to 304 (NOT_MODIFIED), no further processing.
|
||||
<3> Continue with request processing.
|
||||
|
||||
Note that there are 3 variants for this:
|
||||
There are 3 variants for checking conditional requests against eTag values, lastModified
|
||||
values, or both. For conditional "GET" and "HEAD" requests, the response may be set to
|
||||
304 (NOT_MODIFIED). For conditional "POST", "PUT", and "DELETE", the response would be set
|
||||
to 409 (PRECONDITION_FAILED) instead to prevent concurrent modification.
|
||||
|
||||
* `request.checkNotModified(lastModified)` compares lastModified with the
|
||||
`'If-Modified-Since'` or `'If-Unmodified-Since'` request header
|
||||
* `request.checkNotModified(eTag)` compares eTag with the `'If-None-Match'` request header
|
||||
* `request.checkNotModified(eTag, lastModified)` does both, meaning that both
|
||||
conditions should be valid
|
||||
|
||||
When receiving conditional `'GET'`/`'HEAD'` requests, `checkNotModified` will check
|
||||
that the resource has not been modified and if so, it will result in a `HTTP 304 Not Modified`
|
||||
response. In case of conditional `'POST'`/`'PUT'`/`'DELETE'` requests, `checkNotModified`
|
||||
will check that the resource has not been modified and if it has been, it will result in a
|
||||
`HTTP 409 Precondition Failed` response to prevent concurrent modifications.
|
||||
|
||||
[[mvc-caching-static-resources]]
|
||||
=== Static resources
|
||||
|
||||
Static resources should be served with a "Cache-Control" and conditional response headers
|
||||
for optimal performance. See section on configuring <<mvc-config-static-resources>>.
|
||||
|
||||
|
||||
|
||||
[[mvc-httpcaching-shallowetag]]
|
||||
=== ETag Filter
|
||||
|
||||
Support for ETags is provided by the Servlet filter `ShallowEtagHeaderFilter`. It is a
|
||||
plain Servlet Filter, and thus can be used in combination with any web framework. The
|
||||
`ShallowEtagHeaderFilter` filter creates so-called shallow ETags by caching the content
|
||||
written to the response and generating an MD5 hash over that to send as an ETag header.
|
||||
The next time a client sends a request for the same resource, it uses that hash as the
|
||||
`If-None-Match` value. The filter detects this, lets the request be processed as usual, and
|
||||
at the end compares the two hashes. If they are equal, a `304` is returned.
|
||||
|
||||
Note that this strategy saves network bandwidth but not CPU, as the full response must be
|
||||
computed for each request. Other strategies at the controller level, described above, can
|
||||
avoid computation.
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
|
||||
like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
|
||||
https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
|
||||
The `ShallowEtagHeaderFilter` can be used to add "shallow" eTag values, computed from the
|
||||
response content and thus saving bandwith but not CPU time. See <<filters-shallow-etag>>.
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue