|
|
|
[[mvc-caching]]
|
|
|
|
= HTTP Caching
|
|
|
|
|
|
|
|
[.small]#xref:web/webflux/caching.adoc[See equivalent in the Reactive stack]#
|
|
|
|
|
|
|
|
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 (for example, browser)
|
|
|
|
and public (for example, proxy) caches on 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.
|
|
|
|
|
|
|
|
This section describes the HTTP caching-related options that are available in Spring Web MVC.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[mvc-caching-cachecontrol]]
|
|
|
|
== `CacheControl`
|
|
|
|
[.small]#xref:web/webflux/caching.adoc#webflux-caching-cachecontrol[See equivalent in the Reactive stack]#
|
|
|
|
|
|
|
|
{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:
|
|
|
|
|
|
|
|
* {api-spring-framework}/web/servlet/mvc/WebContentInterceptor.html[`WebContentInterceptor`]
|
|
|
|
* {api-spring-framework}/web/servlet/support/WebContentGenerator.html[`WebContentGenerator`]
|
|
|
|
* xref:web/webmvc/mvc-caching.adoc#mvc-caching-etag-lastmodified[Controllers]
|
|
|
|
* xref:web/webmvc/mvc-caching.adoc#mvc-caching-static-resources[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 that focuses on the common scenarios:
|
|
|
|
|
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
----
|
|
|
|
// 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();
|
|
|
|
----
|
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
----
|
|
|
|
// Cache for an hour - "Cache-Control: max-age=3600"
|
|
|
|
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)
|
|
|
|
|
|
|
|
// Prevent caching - "Cache-Control: no-store"
|
|
|
|
val 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"
|
|
|
|
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()
|
|
|
|
----
|
|
|
|
======
|
|
|
|
|
|
|
|
`WebContentGenerator` also accepts a simpler `cachePeriod` property (defined in seconds) that
|
|
|
|
works as follows:
|
|
|
|
|
|
|
|
* A `-1` value does not generate a `Cache-Control` response header.
|
|
|
|
* A `0` value prevents caching by using the `'Cache-Control: no-store'` directive.
|
|
|
|
* An `n > 0` value caches the given response for `n` seconds by using the
|
|
|
|
`'Cache-Control: max-age=n'` directive.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[mvc-caching-etag-lastmodified]]
|
|
|
|
== Controllers
|
|
|
|
[.small]#xref:web/webflux/caching.adoc#webflux-caching-etag-lastmodified[See equivalent in the Reactive stack]#
|
|
|
|
|
|
|
|
Controllers can add explicit support for HTTP caching. We recommended doing so, 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` header and `Cache-Control`
|
|
|
|
settings to a `ResponseEntity`, as the following example shows:
|
|
|
|
|
|
|
|
--
|
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
----
|
|
|
|
@GetMapping("/book/{id}")
|
|
|
|
public ResponseEntity<Book> 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);
|
|
|
|
}
|
|
|
|
----
|
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
----
|
|
|
|
@GetMapping("/book/{id}")
|
|
|
|
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {
|
|
|
|
|
|
|
|
val book = findBook(id);
|
|
|
|
val version = book.getVersion()
|
|
|
|
|
|
|
|
return ResponseEntity
|
|
|
|
.ok()
|
|
|
|
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
|
|
|
|
.eTag(version) // lastModified is also available
|
|
|
|
.body(book)
|
|
|
|
}
|
|
|
|
----
|
|
|
|
======
|
|
|
|
--
|
|
|
|
|
|
|
|
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
|
|
|
|
to the conditional request headers indicates that the content has not changed. Otherwise, the
|
|
|
|
`ETag` and `Cache-Control` headers are added to the response.
|
|
|
|
|
|
|
|
You can also make the check against conditional request headers in the controller,
|
|
|
|
as the following example shows:
|
|
|
|
|
|
|
|
--
|
|
|
|
[tabs]
|
|
|
|
======
|
|
|
|
Java::
|
|
|
|
+
|
|
|
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
|
|
|
----
|
|
|
|
@RequestMapping
|
|
|
|
public String myHandleMethod(WebRequest request, Model model) {
|
|
|
|
|
|
|
|
long eTag = ... // <1>
|
|
|
|
|
|
|
|
if (request.checkNotModified(eTag)) {
|
|
|
|
return null; // <2>
|
|
|
|
}
|
|
|
|
|
|
|
|
model.addAttribute(...); // <3>
|
|
|
|
return "myViewName";
|
|
|
|
}
|
|
|
|
----
|
|
|
|
<1> Application-specific calculation.
|
|
|
|
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
|
|
|
<3> Continue with the request processing.
|
|
|
|
|
|
|
|
Kotlin::
|
|
|
|
+
|
|
|
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
|
|
|
----
|
|
|
|
@RequestMapping
|
|
|
|
fun myHandleMethod(request: WebRequest, model: Model): String? {
|
|
|
|
|
|
|
|
val eTag: Long = ... // <1>
|
|
|
|
|
|
|
|
if (request.checkNotModified(eTag)) {
|
|
|
|
return null // <2>
|
|
|
|
}
|
|
|
|
|
|
|
|
model[...] = ... // <3>
|
|
|
|
return "myViewName"
|
|
|
|
}
|
|
|
|
----
|
|
|
|
<1> Application-specific calculation.
|
|
|
|
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
|
|
|
<3> Continue with the request processing.
|
|
|
|
======
|
|
|
|
--
|
|
|
|
|
|
|
|
|
|
|
|
There are three variants for checking conditional requests against `eTag` values, `lastModified`
|
|
|
|
values, or both. For conditional `GET` and `HEAD` requests, you can set the response to
|
|
|
|
304 (NOT_MODIFIED). For conditional `POST`, `PUT`, and `DELETE`, you can instead set the response
|
|
|
|
to 412 (PRECONDITION_FAILED), to prevent concurrent modification.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[mvc-caching-static-resources]]
|
|
|
|
== Static Resources
|
|
|
|
[.small]#xref:web/webflux/caching.adoc#webflux-caching-static-resources[See equivalent in the Reactive stack]#
|
|
|
|
|
|
|
|
You should serve static resources with a `Cache-Control` and conditional response headers
|
|
|
|
for optimal performance. See the section on configuring xref:web/webmvc/mvc-config/static-resources.adoc[Static Resources].
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[[mvc-httpcaching-shallowetag]]
|
|
|
|
== `ETag` Filter
|
|
|
|
|
|
|
|
You can use the `ShallowEtagHeaderFilter` to add "`shallow`" `eTag` values that are computed from the
|
|
|
|
response content and, thus, save bandwidth but not CPU time. See xref:web/webmvc/filters.adoc#filters-shallow-etag[Shallow ETag].
|
|
|
|
|