How to enable HTTP response caching in Spring Boot
There are a lot of ways in spring boot for http caching. Using spring boot 2.1.1 and additionally spring security 5.1.1.
1. For resources using resourcehandler in code:
You can add customized extensions of resources this way.
registry.addResourceHandler
Is for adding the uri path where to get the resource
.addResourceLocations
Is for setting the location in the filesystem where the resources are located( given is a relative with classpath but absolute path with file::// is also possible.)
.setCacheControl
Is for setting the cache headers (self explanatory.)
Resourcechain and resolver are optional (in this case exactly as the default values.)
@Configuration
public class CustomWebMVCConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*.js", "/*.css", "/*.ttf", "/*.woff", "/*.woff2", "/*.eot",
"/*.svg")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
.cachePrivate()
.mustRevalidate())
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
2. For resources using application properties config file
Same as above, minus the specific patterns, but now as config. This configuration is applied to all resources in the static-locations listed.
spring.resources.cache.cachecontrol.cache-private=true
spring.resources.cache.cachecontrol.must-revalidate=true
spring.resources.cache.cachecontrol.max-age=31536000
spring.resources.static-locations=classpath:/static/
3. At controller level
Response here is the HttpServletResponse injected in the controller method as parameter.
no-cache, must-revalidate, private
getHeaderValue will output the cache options as string. e.g.
response.setHeader(HttpHeaders.CACHE_CONTROL,
CacheControl.noCache()
.cachePrivate()
.mustRevalidate()
.getHeaderValue());
Turns out the no-cache HTTP headers are set by Spring Security. This is discussed in http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers.
The following disables the HTTP response header Pragma: no-cache
, but doesn't otherwise solve the problem:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// Prevent the HTTP response header of "Pragma: no-cache".
http.headers().cacheControl().disable();
}
}
I ended up disabling Spring Security completely for public static resources as following (in the same class as above):
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/public/**");
}
This requires configuring two resource handlers to get cache control headers right:
@Configuration
public class MvcConfigurer extends WebMvcConfigurerAdapter
implements EmbeddedServletContainerCustomizer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Resources without Spring Security. No cache control response headers.
registry.addResourceHandler("/static/public/**")
.addResourceLocations("classpath:/static/public/");
// Resources controlled by Spring Security, which
// adds "Cache-Control: must-revalidate".
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600*24);
}
}
See also Serving static web resources in Spring Boot & Spring Security application.
I have found this Spring extension: https://github.com/foo4u/spring-mvc-cache-control.
You just have to do three steps.
Step 1 (pom.xml):
<dependency>
<groupId>net.rossillo.mvc.cache</groupId>
<artifactId>spring-mvc-cache-control</artifactId>
<version>1.1.1-RELEASE</version>
<scope>compile</scope>
</dependency>
Step 2 (WebMvcConfiguration.java):
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CacheControlHandlerInterceptor());
}
}
Step 3 (Controller):
@Controller
public class MyRestController {
@CacheControl(maxAge=31556926)
@RequestMapping(value = "/someUrl", method = RequestMethod.GET)
public @ResponseBody ResponseEntity<String> myMethod(
HttpServletResponse httpResponse) throws SQLException {
return new ResponseEntity<String>("{}", HttpStatus.OK);
}
}
Overriding of default caching behavior for a particular method can be done in the below way:
@Controller
public class MyRestController {
@RequestMapping(value = "/someUrl", method = RequestMethod.GET)
public @ResponseBody ResponseEntity<String> myMethod(
HttpServletResponse httpResponse) throws SQLException {
return new ResponseEntity.ok().cacheControl(CacheControl.maxAge(100, TimeUnit.SECONDS)).body(T)
}
}