You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.8 KiB
206 lines
6.8 KiB
[[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]. |
|
|
|
|