From fe1642be6f13d0f3c886cd8c023df4fccabade51 Mon Sep 17 00:00:00 2001 From: chirag d Date: Tue, 4 Feb 2020 22:19:12 +0530 Subject: [PATCH] BAEL3771: Cache Headers in Spring MVC --- .../baeldung/spring/web/config/WebConfig.java | 25 +++++- .../controller/CacheControlController.java | 55 ++++++++++++ .../src/main/webapp/resources/hello.css | 0 ...CacheControlControllerIntegrationTest.java | 84 +++++++++++++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 spring-mvc-java/src/main/java/com/baeldung/web/controller/CacheControlController.java create mode 100644 spring-mvc-java/src/main/webapp/resources/hello.css create mode 100644 spring-mvc-java/src/test/java/com/baeldung/web/controller/CacheControlControllerIntegrationTest.java diff --git a/spring-mvc-java/src/main/java/com/baeldung/spring/web/config/WebConfig.java b/spring-mvc-java/src/main/java/com/baeldung/spring/web/config/WebConfig.java index 96e300464b..5cc165680f 100644 --- a/spring-mvc-java/src/main/java/com/baeldung/spring/web/config/WebConfig.java +++ b/spring-mvc-java/src/main/java/com/baeldung/spring/web/config/WebConfig.java @@ -2,6 +2,7 @@ package com.baeldung.spring.web.config; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; @@ -9,15 +10,19 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.http.CacheControl; import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; +import org.springframework.web.servlet.mvc.WebContentInterceptor; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; import org.springframework.web.util.UrlPathHelper; @@ -30,14 +35,14 @@ import com.baeldung.excel.ExcelPOIHelper; @EnableWebMvc @Configuration -@ComponentScan(basePackages = { "com.baeldung.web.controller" }) +@ComponentScan(basePackages = {"com.baeldung.web.controller"}) public class WebConfig implements WebMvcConfigurer { - + @Override public void addViewControllers(final ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); } - + @Bean public ViewResolver thymeleafViewResolver() { final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); @@ -84,7 +89,10 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { - registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/") + .setCacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS) + .noTransform() + .mustRevalidate()); } @Override @@ -121,4 +129,13 @@ public class WebConfig implements WebMvcConfigurer { public ExcelPOIHelper excelPOIHelper() { return new ExcelPOIHelper(); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + WebContentInterceptor interceptor = new WebContentInterceptor(); + interceptor.addCacheMapping(CacheControl.maxAge(60, TimeUnit.SECONDS) + .noTransform() + .mustRevalidate(), "/cache/*"); + registry.addInterceptor(interceptor); + } } \ No newline at end of file diff --git a/spring-mvc-java/src/main/java/com/baeldung/web/controller/CacheControlController.java b/spring-mvc-java/src/main/java/com/baeldung/web/controller/CacheControlController.java new file mode 100644 index 0000000000..f1614ab448 --- /dev/null +++ b/spring-mvc-java/src/main/java/com/baeldung/web/controller/CacheControlController.java @@ -0,0 +1,55 @@ +package com.baeldung.web.controller; + +import org.springframework.http.CacheControl; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.context.request.WebRequest; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + +@Controller +public class CacheControlController { + + @RequestMapping(value = "/hello/{name}", method = RequestMethod.GET) + public ResponseEntity helloWorld(@PathVariable String name) { + CacheControl cacheControl = CacheControl.maxAge(60, TimeUnit.SECONDS) + .noTransform() + .mustRevalidate(); + return ResponseEntity.ok() + .cacheControl(cacheControl) + .body("Hello " + name); + } + + @RequestMapping(value = "/home/{name}", method = RequestMethod.GET) + public String home(@PathVariable String name, final HttpServletResponse response) { + response.addHeader("Cache-Control", "max-age=60, must-revalidate, no-transform"); + return "home"; + } + + @RequestMapping(value = "/cache/{name}", method = RequestMethod.GET) + public ResponseEntity intercept(@PathVariable String name) { + return ResponseEntity.ok().body("Hello " + name); + } + + @RequestMapping(value = "/validate/{name}", method = RequestMethod.GET) + public ResponseEntity validate(@PathVariable String name, WebRequest request) { + + ZoneId zoneId = ZoneId.of("GMT"); + long lastModifiedTimestamp = LocalDateTime.of(2020, 02, 4, 19, 57, 45) + .atZone(zoneId).toInstant().toEpochMilli(); + + if (request.checkNotModified(lastModifiedTimestamp)) { + return ResponseEntity.status(304).build(); + } + + return ResponseEntity.ok().body("Hello " + name); + } + + +} diff --git a/spring-mvc-java/src/main/webapp/resources/hello.css b/spring-mvc-java/src/main/webapp/resources/hello.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-mvc-java/src/test/java/com/baeldung/web/controller/CacheControlControllerIntegrationTest.java b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CacheControlControllerIntegrationTest.java new file mode 100644 index 0000000000..b03f628b20 --- /dev/null +++ b/spring-mvc-java/src/test/java/com/baeldung/web/controller/CacheControlControllerIntegrationTest.java @@ -0,0 +1,84 @@ +package com.baeldung.web.controller; + +import com.baeldung.spring.web.config.WebConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static org.springframework.http.HttpHeaders.IF_UNMODIFIED_SINCE; + +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration(classes = {WebConfig.class, WebConfig.class}) +public class CacheControlControllerIntegrationTest { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @BeforeEach + void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + void whenResponseBody_thenReturnCacheHeader() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/baeldung")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform")); + } + + @Test + void whenViewName_thenReturnCacheHeader() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/home/baeldung")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform")) + .andExpect(MockMvcResultMatchers.view().name("home")); + } + + @Test + void whenStaticResources_thenReturnCacheHeader() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/resources/hello.css")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform")); + } + + @Test + void whenInterceptor_thenReturnCacheHeader() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/cache/baeldung")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.header().string("Cache-Control","max-age=60, must-revalidate, no-transform")); + } + + @Test + void whenValidate_thenReturnCacheHeader() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add(IF_UNMODIFIED_SINCE, "Tue, 04 Feb 2020 19:57:25 GMT"); + this.mockMvc.perform(MockMvcRequestBuilders.get("/validate/baeldung").headers(headers)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().is(304)); + } + + + + +}