Browse Source

Polish Spring MVC docs on HTTP Caching

Issue: SPR-16395
pull/1856/head
Rossen Stoyanchev 7 years ago
parent
commit
4a435c12f2
  1. 202
      src/docs/asciidoc/web/webmvc.adoc

202
src/docs/asciidoc/web/webmvc.adoc

@ -1091,11 +1091,18 @@ configure the `ForwardedHeaderFilter` to remove and ignore such headers. @@ -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 @@ -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. @@ -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 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).
=== Controllers
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: @@ -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: @@ -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.
[[mvc-httpcaching-shallowetag]]
=== ETag Filter
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…
Cancel
Save