How to enable HTTP response caching in Spring Boot
Asked Answered
Y

9

75

I have implemented a REST server using Spring Boot 1.0.2. I'm having trouble preventing Spring from setting HTTP headers that disable HTTP caching.

My controller is as following:

@Controller
public class MyRestController {
    @RequestMapping(value = "/someUrl", method = RequestMethod.GET)
    public @ResponseBody ResponseEntity<String> myMethod(
            HttpServletResponse httpResponse) throws SQLException {
        return new ResponseEntity<String>("{}", HttpStatus.OK);
    }
}

All HTTP responses contain the following headers:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache

I've tried the following to remove or change those headers:

  1. Call setCacheSeconds(-1) in the controller.
  2. Call httpResponse.setHeader("Cache-Control", "max-age=123") in the controller.
  3. Define @Bean that returns WebContentInterceptor for which I've called setCacheSeconds(-1).
  4. Set property spring.resources.cache-period to -1 or a positive value in application.properties.

None of the above have had any effect. How do I disable or change these headers for all or individual requests in Spring Boot?

Yesman answered 11/6, 2014 at 13:19 Comment(2)
I don't think Spring Boot does that (not in any of the samples I tried anyway). Maybe you can share a minimal project that has these headers in the responses?Synonymize
You are right. The culprit turned out to be Spring Security.Yesman
Y
67

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.

Yesman answered 12/6, 2014 at 7:53 Comment(2)
There's no need to completely turn off Spring Security if you want to have different Cache-Control headers for specific controller actions as outlined in https://mcmap.net/q/213096/-spring-security-no-way-to-avoid-cache-control.Sheelagh
This help me (cacheControl().disable()) to solve a problem I had which was @font-face was not loading at the first time.Fishing
H
25

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());
Headache answered 13/1, 2019 at 3:30 Comment(0)
R
13

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 ResponseEntity.ok().cacheControl(CacheControl.maxAge(100, TimeUnit.SECONDS)).body(T)
    }
}
Rogovy answered 11/4, 2020 at 14:46 Comment(0)
T
8

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);
    }
}
Tobiastobie answered 18/8, 2016 at 17:36 Comment(1)
seems the dependancy spring-mvc-cache-control has not been updated since 2015 and has several known vulnerabilities since it refers to an old Spring MVC :-/Dulla
S
5

The CacheControl class is a fluent builder, which makes it easy for us to create different types of caching:

@GetMapping("/users/{name}")
public ResponseEntity<UserDto> getUser(@PathVariable String name) { 
    return ResponseEntity.ok()
      .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
      .body(new UserDto(name));
}

Let's hit this endpoint in our test, and assert that we have changed the headers:

given()
  .when()
  .get(getBaseUrl() + "/users/Michael")
  .then()
  .header("Cache-Control", "max-age=60");
Shurlock answered 26/6, 2021 at 8:26 Comment(0)
D
0

I run into similar problem. I wanted to get just some of dynamic resources (images) cached in the browser. If image changes (not very often) I change the part of uri... This is my sollution

    http.headers().cacheControl().disable();
    http.headers().addHeaderWriter(new HeaderWriter() {

        CacheControlHeadersWriter originalWriter = new CacheControlHeadersWriter();

        @Override
        public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
            Collection<String> headerNames = response.getHeaderNames();
            String requestUri = request.getRequestURI();
            if(!requestUri.startsWith("/web/eventImage")) {
                originalWriter.writeHeaders(request, response);
            } else {
               //write header here or do nothing if it was set in the code
            }       
        }
    });
Dingy answered 29/3, 2016 at 19:35 Comment(2)
Note that the code can be shortened to: http.headers().cacheControl().disable(); http.headers().addHeaderWriter(new DelegatingRequestMatcherHeaderWriter( new NegatedRequestMatcher(new AntPathRequestMatcher("/web/eventImage")), new CacheControlHeadersWriter() ));Astounding
What should we do with the headerNames variable? It's not being used.Jodi
C
0
@Configuration
@EnableAutoConfiguration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/resources/")
                .setCachePeriod(31556926);

    }
}
Congius answered 23/12, 2016 at 9:55 Comment(1)
this is for static resources only.Bushranger
C
0

If you don't care to have your static resources authenticated, you could do this:

import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toStaticResources;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
    @Override
    public void configure(WebSecurity webSecurity) throws Exception {
        webSecurity
                .ignoring()
                .requestMatchers(toStaticResources().atCommonLocations());
    }
...
}

and in your application.properties:

spring.resources.cache.cachecontrol.max-age=43200

See ResourceProperties.java for more properties that can be set.

Characteristic answered 11/10, 2018 at 6:4 Comment(1)
Using spring boot 2.1.1 and spring security 5.1.1, spring.resources.cache.cachecontrol.max-age=43200 works even if no ignoring is done in configure method. As this configuration in application.properties overrides the spring security cache headers for resources. One thing noted is that this does not apply to the favicon.ico instead it gets the normal cache headers by spring security.Headache
V
0

I used below lines in my controller.

ResponseEntity.ok().cacheControl(CacheControl.maxAge(secondWeWantTobeCached, TimeUnit.SECONDS)).body(objToReturnInResponse);

Please note that Response will have header Cache-Control with value secondWeWantTobeCached. However if we are typing url in addressbar and pressing enter, Request will always be sent from Chrome to server. However if we hit url from some link, browser will not send a new request and it will be taken from cache.

Vennieveno answered 13/8, 2019 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.