Skip to content

Commit 516937c

Browse files
committed
Polish Spring MVC docs on HTTP Caching
Issue: SPR-16395
1 parent dac97f1 commit 516937c

File tree

1 file changed

+69
-133
lines changed

1 file changed

+69
-133
lines changed

src/docs/asciidoc/web/webmvc.adoc

Lines changed: 69 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,11 +1091,18 @@ configure the `ForwardedHeaderFilter` to remove and ignore such headers.
10911091
[[filters-shallow-etag]]
10921092
=== Shallow ETag
10931093

1094-
There is a `ShallowEtagHeaderFilter`. It is called shallow because it doesn't have any
1095-
knowledge of the content. Instead it relies on buffering actual content written to the
1096-
response and computing the ETag value at the end.
1094+
The `ShallowEtagHeaderFilter` filter creates a "shallow" ETag by caching the content
1095+
written to the response, and computing an MD5 hash from it. The next time a client sends,
1096+
it does the same, but also compares the computed value against the `If-None-Match` request
1097+
header and if the two are equal, it returns a 304 (NOT_MODIFIED).
10971098

1098-
See <<mvc-httpcaching-shallowetag>> for more details.
1099+
This strategy saves network bandwidth but not CPU, as the full response must be computed
1100+
for each request. Other strategies at the controller level, described above, can avoid the
1101+
computation. See <<mvc-caching>>.
1102+
1103+
This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
1104+
like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
1105+
https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
10991106

11001107

11011108

@@ -3736,46 +3743,33 @@ http://hdiv.org/[HDIV] is another web security framework that integrates with Sp
37363743
[[mvc-caching]]
37373744
== HTTP Caching
37383745

3739-
A good HTTP caching strategy can significantly improve the performance of a web application
3740-
and the experience of its clients. The `'Cache-Control'` HTTP response header is mostly
3741-
responsible for this, along with conditional headers such as `'Last-Modified'` and `'ETag'`.
3746+
HTTP caching can significantly improve the performance of a web application. HTTP caching
3747+
revolves around the "Cache-Control" response header and subsequently conditional request
3748+
headers such as "Last-Modified" and "ETag". "Cache-Control" advises private (e.g. browser)
3749+
and public (e.g. proxy) caches how to cache and re-use responses. An "ETag" header is used
3750+
to make a conditional request that may result in a 304 (NOT_MODIFIED) without a body,
3751+
if the content has not changed. "ETag" can be seen as a more sophisticated successor to
3752+
the `Last-Modified` header.
37423753

3743-
The `'Cache-Control'` HTTP response header advises private caches (e.g. browsers) and
3744-
public caches (e.g. proxies) on how they can cache HTTP responses for further reuse.
3745-
3746-
An http://en.wikipedia.org/wiki/HTTP_ETag[ETag] (entity tag) is an HTTP response header
3747-
returned by an HTTP/1.1 compliant web server used to determine change in content at a
3748-
given URL. It can be considered to be the more sophisticated successor to the
3749-
`Last-Modified` header. When a server returns a representation with an ETag header, the
3750-
client can use this header in subsequent GETs, in an `If-None-Match` header. If the
3751-
content has not changed, the server returns `304: Not Modified`.
3752-
3753-
This section describes the different choices available to configure HTTP caching in a
3754-
Spring Web MVC application.
3754+
This section describes HTTP caching related options available in Spring Web MVC.
37553755

37563756

37573757

37583758
[[mvc-caching-cachecontrol]]
3759-
=== Cache-Control
3759+
=== `CacheControl`
37603760

3761-
Spring Web MVC supports many use cases and ways to configure "Cache-Control" headers for
3762-
an application. While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234 Section 5.2.2]
3763-
completely describes that header and its possible directives, there are several ways to
3764-
address the most common cases.
3761+
{api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for
3762+
configuring settings related to the "Cache-Control" header and is accepted as an argument
3763+
in a number of places:
37653764

3766-
Spring Web MVC uses a configuration convention in several of its APIs:
3767-
`setCachePeriod(int seconds)`:
3768-
3769-
* A `-1` value won't generate a `'Cache-Control'` response header.
3770-
* A `0` value will prevent caching using the `'Cache-Control: no-store'` directive.
3771-
* An `n > 0` value will cache the given response for `n` seconds using the
3772-
`'Cache-Control: max-age=n'` directive.
3773-
3774-
The {api-spring-framework}/http/CacheControl.html[`CacheControl`] builder
3775-
class simply describes the available "Cache-Control" directives and makes it easier to
3776-
build your own HTTP caching strategy. Once built, a `CacheControl` instance can then be
3777-
accepted as an argument in several Spring Web MVC APIs.
3765+
* {api-spring-framework}/web/servlet/mvc/WebContentInterceptor.html[`WebContentInterceptor`]
3766+
* {api-spring-framework}/web/servlet/support/WebContentGenerator.html[`WebContentGenerator`]
3767+
* <<mvc-caching-etag-lastmodified>>
3768+
* <<mvc-caching-static-resources>>
37783769

3770+
While https://tools.ietf.org/html/rfc7234#section-5.2.2[RFC 7234] describes all possible
3771+
directives for the "Cache-Control" response header, the `CacheControl` type takes a
3772+
use case oriented approach focusing on the common scenarios:
37793773

37803774
[source,java,indent=0]
37813775
[subs="verbatim,quotes"]
@@ -3789,65 +3783,26 @@ accepted as an argument in several Spring Web MVC APIs.
37893783
// Cache for ten days in public and private caches,
37903784
// public caches should not transform the response
37913785
// "Cache-Control: max-age=864000, public, no-transform"
3792-
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
3793-
.noTransform().cachePublic();
3786+
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
37943787
----
37953788

3789+
`WebContentGenerator` also accept a simpler `cachePeriod` property, in seconds, that
3790+
works as follows:
37963791

3797-
3798-
[[mvc-caching-static-resources]]
3799-
=== Static resources
3800-
3801-
Static resources should be served with appropriate `'Cache-Control'` and conditional
3802-
headers for optimal performance.
3803-
<<mvc-config-static-resources,Configuring a `ResourceHttpRequestHandler`>> for serving
3804-
static resources not only natively writes `'Last-Modified'` headers by reading a file's
3805-
metadata, but also `'Cache-Control'` headers if properly configured.
3806-
3807-
You can set the `cachePeriod` attribute on a `ResourceHttpRequestHandler` or use
3808-
a `CacheControl` instance, which supports more specific directives:
3809-
3810-
[source,java,indent=0]
3811-
[subs="verbatim"]
3812-
----
3813-
@Configuration
3814-
@EnableWebMvc
3815-
public class WebConfig implements WebMvcConfigurer {
3816-
3817-
@Override
3818-
public void addResourceHandlers(ResourceHandlerRegistry registry) {
3819-
registry.addResourceHandler("/resources/**")
3820-
.addResourceLocations("/public-resources/")
3821-
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
3822-
}
3823-
3824-
}
3825-
----
3826-
3827-
And in XML:
3828-
3829-
[source,xml,indent=0]
3830-
[subs="verbatim"]
3831-
----
3832-
<mvc:resources mapping="/resources/**" location="/public-resources/">
3833-
<mvc:cache-control max-age="3600" cache-public="true"/>
3834-
</mvc:resources>
3835-
----
3792+
* A `-1` value won't generate a "Cache-Control" response header.
3793+
* A `0` value will prevent caching using the `'Cache-Control: no-store'` directive.
3794+
* An `n > 0` value will cache the given response for `n` seconds using the
3795+
`'Cache-Control: max-age=n'` directive.
38363796

38373797

38383798

38393799
[[mvc-caching-etag-lastmodified]]
3840-
=== @Controller caching
3841-
3842-
Controllers can support `'Cache-Control'`, `'ETag'`, and/or `'If-Modified-Since'` HTTP requests;
3843-
this is indeed recommended if a `'Cache-Control'` header is to be set on the response.
3844-
This involves calculating a lastModified `long` and/or an Etag value for a given request,
3845-
comparing it against the `'If-Modified-Since'` request header value, and potentially returning
3846-
a response with status code 304 (Not Modified).
3800+
=== Controllers
38473801

3848-
As described in <<mvc-ann-httpentity>>, controllers can interact with the request/response using
3849-
`HttpEntity` types. Controllers returning `ResponseEntity` can include HTTP caching information
3850-
in responses like this:
3802+
Controllers can add explicit support for HTTP caching. This is recommended since the
3803+
lastModified or ETag value for a resource needs to be calculated before it can be compared
3804+
against conditional request headers. A controller can add an ETag and "Cache-Control"
3805+
settings to a `ResponseEntity`:
38513806

38523807
[source,java,indent=0]
38533808
[subs="verbatim,quotes"]
@@ -3859,79 +3814,60 @@ in responses like this:
38593814
String version = book.getVersion();
38603815
38613816
return ResponseEntity
3862-
.ok()
3863-
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
3864-
.eTag(version) // lastModified is also available
3865-
.body(book);
3817+
.ok()
3818+
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
3819+
.eTag(version) // lastModified is also available
3820+
.body(book);
38663821
}
38673822
----
38683823

3869-
Doing this will not only include `'ETag'` and `'Cache-Control'` headers in the response, it will **also convert the
3870-
response to an `HTTP 304 Not Modified` response with an empty body** if the conditional headers sent by the client
3871-
match the caching information set by the Controller.
3824+
This will send an 304 (NOT_MODIFIED) response with an empty body, if the comparison
3825+
to the conditional request headers indicates the content has not changed. Otherwise the
3826+
"ETag" and "Cache-Control" headers will be added to the response.
38723827

3873-
An `@RequestMapping` method may also wish to support the same behavior.
3874-
This can be achieved as follows:
3828+
The check against conditional request headers can also be made in the controller:
38753829

38763830
[source,java,indent=0]
38773831
[subs="verbatim,quotes"]
38783832
----
38793833
@RequestMapping
38803834
public String myHandleMethod(WebRequest webRequest, Model model) {
38813835
3882-
long lastModified = // 1. application-specific calculation
3836+
long eTag = ... <1>
38833837
3884-
if (request.checkNotModified(lastModified)) {
3885-
// 2. shortcut exit - no further processing necessary
3886-
return null;
3838+
if (request.checkNotModified(eTag)) {
3839+
return null; <2>
38873840
}
38883841
3889-
// 3. or otherwise further request processing, actually preparing content
3890-
model.addAttribute(...);
3842+
model.addAttribute(...); <3>
38913843
return "myViewName";
38923844
}
38933845
----
38943846

3895-
There are two key elements here: calling `request.checkNotModified(lastModified)` and
3896-
returning `null`. The former sets the appropriate response status and headers
3897-
before it returns `true`.
3898-
The latter, in combination with the former, causes Spring MVC to do no further
3899-
processing of the request.
3847+
<1> Application-specific calculation.
3848+
<2> Response has been set to 304 (NOT_MODIFIED), no further processing.
3849+
<3> Continue with request processing.
39003850

3901-
Note that there are 3 variants for this:
3851+
There are 3 variants for checking conditional requests against eTag values, lastModified
3852+
values, or both. For conditional "GET" and "HEAD" requests, the response may be set to
3853+
304 (NOT_MODIFIED). For conditional "POST", "PUT", and "DELETE", the response would be set
3854+
to 409 (PRECONDITION_FAILED) instead to prevent concurrent modification.
39023855

3903-
* `request.checkNotModified(lastModified)` compares lastModified with the
3904-
`'If-Modified-Since'` or `'If-Unmodified-Since'` request header
3905-
* `request.checkNotModified(eTag)` compares eTag with the `'If-None-Match'` request header
3906-
* `request.checkNotModified(eTag, lastModified)` does both, meaning that both
3907-
conditions should be valid
39083856

3909-
When receiving conditional `'GET'`/`'HEAD'` requests, `checkNotModified` will check
3910-
that the resource has not been modified and if so, it will result in a `HTTP 304 Not Modified`
3911-
response. In case of conditional `'POST'`/`'PUT'`/`'DELETE'` requests, `checkNotModified`
3912-
will check that the resource has not been modified and if it has been, it will result in a
3913-
`HTTP 409 Precondition Failed` response to prevent concurrent modifications.
39143857

3858+
[[mvc-caching-static-resources]]
3859+
=== Static resources
39153860

3861+
Static resources should be served with a "Cache-Control" and conditional response headers
3862+
for optimal performance. See section on configuring <<mvc-config-static-resources>>.
39163863

3917-
[[mvc-httpcaching-shallowetag]]
3918-
=== ETag Filter
39193864

3920-
Support for ETags is provided by the Servlet filter `ShallowEtagHeaderFilter`. It is a
3921-
plain Servlet Filter, and thus can be used in combination with any web framework. The
3922-
`ShallowEtagHeaderFilter` filter creates so-called shallow ETags by caching the content
3923-
written to the response and generating an MD5 hash over that to send as an ETag header.
3924-
The next time a client sends a request for the same resource, it uses that hash as the
3925-
`If-None-Match` value. The filter detects this, lets the request be processed as usual, and
3926-
at the end compares the two hashes. If they are equal, a `304` is returned.
39273865

3928-
Note that this strategy saves network bandwidth but not CPU, as the full response must be
3929-
computed for each request. Other strategies at the controller level, described above, can
3930-
avoid computation.
3866+
[[mvc-httpcaching-shallowetag]]
3867+
=== ETag Filter
39313868

3932-
This filter has a `writeWeakETag` parameter that configures the filter to write Weak ETags,
3933-
like this: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"`, as defined in
3934-
https://tools.ietf.org/html/rfc7232#section-2.3[RFC 7232 Section 2.3].
3869+
The `ShallowEtagHeaderFilter` can be used to add "shallow" eTag values, computed from the
3870+
response content and thus saving bandwith but not CPU time. See <<filters-shallow-etag>>.
39353871

39363872

39373873

0 commit comments

Comments
 (0)