diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc index 9d5bea03db8..92fbf2944bf 100644 --- a/src/asciidoc/web-mvc.adoc +++ b/src/asciidoc/web-mvc.adoc @@ -1911,39 +1911,6 @@ which case they apply to matching controllers. This provides an alternative to u `WebBindingInitializer`. See the <> section for more details. -[[mvc-ann-lastmodified]] -==== Support for the Last-Modified Response Header To Facilitate Content Caching -An `@RequestMapping` method may wish to support `'Last-Modified'` HTTP requests, as -defined in the contract for the Servlet API's `getLastModified` method, to facilitate -content caching. This involves calculating a lastModified `long` 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). An annotated -controller method can achieve that as follows: - -[source,java,indent=0] -[subs="verbatim,quotes"] ----- - @RequestMapping - public String myHandleMethod(WebRequest webRequest, Model model) { - - long lastModified = // 1. application-specific calculation - - if (request.checkNotModified(lastModified)) { - // 2. shortcut exit - no further processing necessary - return null; - } - - // 3. or otherwise further request processing, actually preparing content - model.addAttribute(...); - return "myViewName"; - } ----- - -There are two key elements to note: calling `request.checkNotModified(lastModified)` and -returning `null`. The former sets the response status to 304 before it returns `true`. -The latter, in combination with the former, causes Spring MVC to do no further -processing of the request. - [[mvc-ann-controller-advice]] ==== Advising controllers with the `@ControllerAdvice` annotation The `@ControllerAdvice` annotation is a component annotation allowing implementation @@ -4029,6 +3996,7 @@ You do not need to define a `DefaultRequestToViewNameTranslator` bean explicitly like the default settings of the `DefaultRequestToViewNameTranslator`, you can rely on the Spring Web MVC `DispatcherServlet` to instantiate an instance of this class if one is not explicitly configured. + ==== Of course, if you need to change the default settings, then you do need to configure @@ -4038,9 +4006,16 @@ that can be configured. +[[mvc-caching]] +== HTTP caching support + +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'`. + +The `'Cache-Control'` HTTP response header advice private caches (e.g. browsers) and + public caches (e.g. proxies) on how they can cache HTTP responses for further reuse. -[[mvc-etag]] -== ETag support 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 @@ -4048,6 +4023,163 @@ given URL. It can be considered to be the more sophisticated successor to 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. + + +[[mvc-caching-cachecontrol]] +=== Cache-Control HTTP header + +Spring Web MVC supports many use cases and ways to configure "Cache-Control" headers for +an application. While the 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. + +Spring Web MVC is using 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 +* A `n > 0` value will cache the given response for `n` seconds using the +`'Cache-Control: max-age=n'` directive + +The {javadoc-baseurl}/org/springframework/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 be then +taken as an argument in several Spring Web MVC APIs. + + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + // Cache for an hour - "Cache-Control: max-age=3600" + CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); + + // Prevent caching - "Cache-Control: no-store" + CacheControl ccNoStore = CacheControl.noStore(); + + // 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(); +---- + +[[mvc-caching-static-resources]] +=== HTTP caching support for 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 files +metadata, but also `'Cache-Control'` 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 extends WebMvcConfigurerAdapter { + + @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-caching-etag-lastmodified]] +=== Support for the Cache-Control, ETag and Last-Modified response headers in 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 access request/response using `HttpEntity` types. +Controllers returning `ResponseEntity` can include HTTP caching information in responses like this: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @RequestMapping("/book/{id}") + public ResponseEntity showBook(@PathVariable Long id) { + + Book book = findBook(id); + String version = book.getVersion(); + + return ResponseEntity + .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 a `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. + + +An `@RequestMapping` method may also wish to support the same behavior. +This can be achieved as follows: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @RequestMapping + public String myHandleMethod(WebRequest webRequest, Model model) { + + long lastModified = // 1. application-specific calculation + + if (request.checkNotModified(lastModified)) { + // 2. shortcut exit - no further processing necessary + return null; + } + + // 3. or otherwise further request processing, actually preparing content + model.addAttribute(...); + return "myViewName"; + } +---- + +There are two key elements here: calling `request.checkNotModified(lastModified)` and +returning `null`. The former sets the response status to 304 before it returns `true`. +The latter, in combination with the former, causes Spring MVC to do no further +processing of the request. + +Note that there are 3 variants for this: + +* `request.checkNotModified(lastModified)` compares lastModified with the +`'If-Modified-Since'` request header +* `request.checkNotModified(eTag)` compares eTag with the `'ETag'` request header +* `request.checkNotModified(eTag, lastModified)` does both, meaning that both +conditions should be valid for the server to issue an `HTTP 304 Not Modified` response + + +[[mvc-httpcaching-shallowetag]] +=== Shallow ETag support + 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 (as opposed to deep @@ -4059,6 +4191,10 @@ compares the two hashes. If they are equal, a `304` is returned. This filter wil save processing power, as the view is still rendered. The only thing it saves is bandwidth, as the rendered response is not sent back over the wire. +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 +save network bandwidth and avoid computation. + You configure the `ShallowEtagHeaderFilter` in `web.xml`: [source,xml,indent=0] @@ -4075,6 +4211,24 @@ You configure the `ShallowEtagHeaderFilter` in `web.xml`: ---- +Or in Servlet 3.0+ environments, + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { + + // ... + + @Override + protected Filter[] getServletFilters() { + return new Filter[] { new ShallowEtagHeaderFilter() }; + } + + } +---- + +See <> for more details. @@ -4656,6 +4810,9 @@ And in XML: ---- +For more details, see <>. + + The `mapping` attribute must be an Ant pattern that can be used by `SimpleUrlHandlerMapping`, and the `location` attribute must specify one or more valid resource directory locations. Multiple resource locations may be specified using a