【问题标题】:How to enable HTTP response caching in Spring Boot如何在 Spring Boot 中启用 HTTP 响应缓存
【发布时间】:2014-08-01 13:47:59
【问题描述】:

我已经使用 Spring Boot 1.0.2 实现了一个 REST 服务器。我无法阻止 Spring 设置禁用 HTTP 缓存的 HTTP 标头。

我的控制器如下:

@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);
    }
}

所有 HTTP 响应都包含以下标头:

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

我尝试了以下方法来删除或更改这些标题:

  1. 在控制器中调用setCacheSeconds(-1)
  2. 在控制器中调用httpResponse.setHeader("Cache-Control", "max-age=123")
  3. 定义返回 WebContentInterceptor@Bean,为此我调用了 setCacheSeconds(-1)
  4. 将属性spring.resources.cache-period 设置为-1 或application.properties 中的正值。

以上都没有任何效果。如何在 Spring Boot 中为所有或单个请求禁用或更改这些标头?

【问题讨论】:

  • 我认为 Spring Boot 不会这样做(无论如何我尝试过的任何示例都没有)。也许您可以分享一个在响应中包含这些标头的最小项目?
  • 你是对的。罪魁祸首原来是 Spring Security。

标签: java spring spring-mvc spring-security spring-boot


【解决方案1】:

原来无缓存 HTTP 标头是由 Spring Security 设置的。这在http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers 中进行了讨论。

以下内容禁用了 HTTP 响应标头 Pragma: no-cache,但没有解决问题:

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();
    }
}

我最终为公共静态资源完全禁用了 Spring Security,如下所示(在与上面相同的类中):

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/static/public/**");
}

这需要配置两个资源处理程序来正确获取缓存控制标头:

@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);
    }
}

另见Serving static web resources in Spring Boot & Spring Security application

【讨论】:

  • 如果你想为stackoverflow.com/a/36459244/759042 中所述的特定控制器操作使用不同的 Cache-Control 标头,则无需完全关闭 Spring Security。
  • 这可以帮助我 (cacheControl().disable()) 解决我遇到的一个问题,即@font-face 没有在第一次加载。
【解决方案2】:

spring boot中有很多方法可以进行http缓存。使用 spring boot 2.1.1 和另外的 spring security 5.1.1。

1.对于在代码中使用资源处理程序的资源:

您可以通过这种方式添加自定义的资源扩展。

registry.addResourceHandler

用于添加获取资源的uri路径

.addResourceLocations

用于设置资源所在文件系统中的位置( given 是类路径的相对路径,但 file::// 的绝对路径也是可能的。)

.setCacheControl

用于设置缓存头(不言自明。)

资源链和解析器是可选的(在这种情况下与默认值完全相同。)

@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。对于使用应用程序属性配置文件的资源

与上面相同,减去特定模式,但现在作为配置。 此配置适用于列出的静态位置中的所有资源。

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.在控制器级别

这里的Response是作为参数注入到控制器方法中的HttpServletResponse。

no-cache, must-revalidate, private

getHeaderValue 将缓存选项输出为字符串。例如

response.setHeader(HttpHeaders.CACHE_CONTROL,
            CacheControl.noCache()
                    .cachePrivate()
                    .mustRevalidate()
                    .getHeaderValue());

【讨论】:

    【解决方案3】:

    我找到了这个 Spring 扩展:https://github.com/foo4u/spring-mvc-cache-control

    您只需执行三个步骤。

    第 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>
    

    第 2 步(WebMvcConfiguration.java):

    @Configuration
    public class WebMvcConfig extends WebMvcConfigurerAdapter implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new CacheControlHandlerInterceptor());
        }
    }
    

    第 3 步(控制器):

    @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);
        }
    }
    

    【讨论】:

      【解决方案4】:

      可以通过以下方式覆盖特定方法的默认缓存行为:

      @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)
          }
      }
      

      【讨论】:

        【解决方案5】:

        CacheControl 类是一个流畅的构建器,它可以让我们轻松创建不同类型的缓存:

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

        让我们在测试中点击这个端点,并断言我们已经更改了标头:

        given()
          .when()
          .get(getBaseUrl() + "/users/Michael")
          .then()
          .header("Cache-Control", "max-age=60");
        

        【讨论】:

          【解决方案6】:

          我遇到了类似的问题。我只想获取浏览器中缓存的一些动态资源(图像)。如果图像更改(不经常),我会更改 uri 的部分......这是我的解决方案

              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
                      }       
                  }
              });
          

          【讨论】:

          • 注意代码可以简写为:http.headers().cacheControl().disable(); http.headers().addHeaderWriter(new DelegatingRequestMatcherHeaderWriter( new NegatedRequestMatcher(new AntPathRequestMatcher("/web/eventImage")), new CacheControlHeadersWriter() ));
          • 我们应该如何处理 headerNames 变量?它没有被使用。
          【解决方案7】:
          @Configuration
          @EnableAutoConfiguration
          public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
          
              @Override
              public void addResourceHandlers(ResourceHandlerRegistry registry) {
          
                  registry.addResourceHandler("/resources/**")
                          .addResourceLocations("/resources/")
                          .setCachePeriod(31556926);
          
              }
          }
          

          【讨论】:

          • 这仅适用于静态资源。
          【解决方案8】:

          如果您不想对静态资源进行身份验证,可以这样做:

          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());
              }
          ...
          }
          

          在你的application.properties:

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

          更多可以设置的属性请见ResourceProperties.java

          【讨论】:

          • 使用 spring boot 2.1.1 和 spring security 5.1.1,即使在 configure 方法中没有忽略 spring.resources.cache.cachecontrol.max-age=43200 也可以工作。由于 application.properties 中的此配置覆盖了资源的 spring 安全缓存标头。需要注意的一件事是,这不适用于 favicon.ico,而是通过 spring security 获取正常的缓存标头。
          【解决方案9】:

          我在控制器中使用了以下行。

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

          请注意,响应将具有值为 secondWeWantTobeCached 的标头 Cache-Control。但是,如果我们在地址栏中输入 url 并按回车,请求将始终从 Chrome 发送到服务器。但是,如果我们从某个链接点击 url,浏览器将不会发送新请求,它将从缓存中获取。

          【讨论】:

            猜你喜欢
            • 2016-08-10
            • 1970-01-01
            • 1970-01-01
            • 2016-12-31
            • 1970-01-01
            • 2011-04-21
            • 2021-12-16
            • 2011-03-25
            相关资源
            最近更新 更多