From 4a435c12f24a08c9dc4de91e672136a587bdbd19 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 11 Jun 2018 16:27:22 -0400 Subject: [PATCH] Polish Spring MVC docs on HTTP Caching Issue: SPR-16395 --- src/docs/asciidoc/web/webmvc.adoc | 202 ++++++++++-------------------- 1 file changed, 69 insertions(+), 133 deletions(-) diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index f0c589a7752..b7f02719e0d 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -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 <> 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 <>. + +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`] +* <> +* <> +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. -<> 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"] ----- - - - ----- +* 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 <>, 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-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 <>.