Browse Source

Relax SPR-13867 changes for ResourceHttpRequestHandler

Prior to this change, SPR-13867 made sure that any class extending
WebContentGenerator would not overwrite existing HTTP "Cache-Control"
response headers - set by a filter, a Controller handler, etc.

This caused issues with resource handling, since specifying a cache
configuration there would not overwrite default headers set by filters,
for example by Spring Security.

This commit restricts the previous changes to the
RequestMappingHandlerAdapter, in order to avoid overwriting header set
by a filter or a Controller handler in those cases.

Issue: SPR-14005
pull/985/head
Brian Clozel 10 years ago
parent
commit
50bcd67fb6
  1. 14
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
  2. 62
      spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
  3. 58
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java
  4. 11
      spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java

14
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

@ -747,11 +747,13 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
mav = invokeHandlerMethod(request, response, handlerMethod); mav = invokeHandlerMethod(request, response, handlerMethod);
} }
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
} applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
else { }
prepareResponse(response); else {
prepareResponse(response);
}
} }
return mav; return mav;
@ -895,7 +897,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
} }
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>(); List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
// Global methods first // Global methods first
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache .entrySet()) { for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) { if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean(); Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) { for (Method method : entry.getValue()) {

62
spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java

@ -75,7 +75,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
private static final String HEADER_EXPIRES = "Expires"; private static final String HEADER_EXPIRES = "Expires";
private static final String HEADER_CACHE_CONTROL = "Cache-Control"; protected static final String HEADER_CACHE_CONTROL = "Cache-Control";
/** Set of supported HTTP methods */ /** Set of supported HTTP methods */
@ -372,16 +372,14 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* @since 4.2 * @since 4.2
*/ */
protected final void applyCacheControl(HttpServletResponse response, CacheControl cacheControl) { protected final void applyCacheControl(HttpServletResponse response, CacheControl cacheControl) {
if (!response.containsHeader(HEADER_CACHE_CONTROL)) { String ccValue = cacheControl.getHeaderValue();
String ccValue = cacheControl.getHeaderValue(); if (ccValue != null) {
if (ccValue != null) { // Set computed HTTP 1.1 Cache-Control header
// Set computed HTTP 1.1 Cache-Control header response.setHeader(HEADER_CACHE_CONTROL, ccValue);
response.setHeader(HEADER_CACHE_CONTROL, ccValue);
if (response.containsHeader(HEADER_PRAGMA)) {
if (response.containsHeader(HEADER_PRAGMA)) { // Reset HTTP 1.0 Pragma header if present
// Reset HTTP 1.0 Pragma header if present response.setHeader(HEADER_PRAGMA, "");
response.setHeader(HEADER_PRAGMA, "");
}
} }
} }
} }
@ -397,32 +395,30 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds) { protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds) {
if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (this.useExpiresHeader || !this.useCacheControlHeader) {
if (this.useExpiresHeader || !this.useCacheControlHeader) { // Deprecated HTTP 1.0 cache behavior, as in previous Spring versions
// Deprecated HTTP 1.0 cache behavior, as in previous Spring versions if (cacheSeconds > 0) {
if (cacheSeconds > 0) { cacheForSeconds(response, cacheSeconds);
cacheForSeconds(response, cacheSeconds); }
} else if (cacheSeconds == 0) {
else if (cacheSeconds == 0) { preventCaching(response);
preventCaching(response); }
}
else {
CacheControl cControl;
if (cacheSeconds > 0) {
cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS);
if (this.alwaysMustRevalidate) {
cControl = cControl.mustRevalidate();
} }
} }
else if (cacheSeconds == 0) {
cControl = (this.useCacheControlNoStore ? CacheControl.noStore() : CacheControl.noCache());
}
else { else {
CacheControl cControl; cControl = CacheControl.empty();
if (cacheSeconds > 0) {
cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS);
if (this.alwaysMustRevalidate) {
cControl = cControl.mustRevalidate();
}
}
else if (cacheSeconds == 0) {
cControl = (this.useCacheControlNoStore ? CacheControl.noStore() : CacheControl.noCache());
}
else {
cControl = CacheControl.empty();
}
applyCacheControl(response, cControl);
} }
applyCacheControl(response, cControl);
} }
} }

58
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterIntegrationTests.java

@ -148,10 +148,10 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test @Test
public void handle() throws Exception { public void handle() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class, Class<?>[] parameterTypes = new Class<?>[] {int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class, Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class, Color.class, HttpServletRequest.class, HttpServletResponse.class, TestBean.class, TestBean.class,
User.class, OtherUser.class, Model.class, UriComponentsBuilder.class }; User.class, OtherUser.class, Model.class, UriComponentsBuilder.class};
String datePattern = "yyyy.MM.dd"; String datePattern = "yyyy.MM.dd";
String formattedDate = "2011.03.16"; String formattedDate = "2011.03.16";
@ -188,12 +188,12 @@ public class RequestMappingHandlerAdapterIntegrationTests {
assertEquals("headerValue", model.get("header")); assertEquals("headerValue", model.get("header"));
assertEquals(date, model.get("dateParam")); assertEquals(date, model.get("dateParam"));
Map<?,?> map = (Map<?,?>) model.get("headerMap"); Map<?, ?> map = (Map<?, ?>) model.get("headerMap");
assertEquals("headerValue", map.get("header")); assertEquals("headerValue", map.get("header"));
assertEquals("anotherHeaderValue", map.get("anotherHeader")); assertEquals("anotherHeaderValue", map.get("anotherHeader"));
assertEquals("systemHeaderValue", model.get("systemHeader")); assertEquals("systemHeaderValue", model.get("systemHeader"));
map = (Map<?,?>) model.get("paramMap"); map = (Map<?, ?>) model.get("paramMap");
assertEquals(formattedDate, map.get("dateParam")); assertEquals(formattedDate, map.get("dateParam"));
assertEquals("paramByConventionValue", map.get("paramByConvention")); assertEquals("paramByConventionValue", map.get("paramByConvention"));
@ -229,7 +229,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test @Test
public void handleRequestBody() throws Exception { public void handleRequestBody() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { byte[].class }; Class<?>[] parameterTypes = new Class<?>[] {byte[].class};
request.setMethod("POST"); request.setMethod("POST");
request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.addHeader("Content-Type", "text/plain; charset=utf-8");
@ -246,7 +246,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test @Test
public void handleAndValidateRequestBody() throws Exception { public void handleAndValidateRequestBody() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { TestBean.class, Errors.class }; Class<?>[] parameterTypes = new Class<?>[] {TestBean.class, Errors.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8")); request.setContent("Hello Server".getBytes("UTF-8"));
@ -262,7 +262,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
@Test @Test
public void handleHttpEntity() throws Exception { public void handleHttpEntity() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { HttpEntity.class }; Class<?>[] parameterTypes = new Class<?>[] {HttpEntity.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8")); request.setContent("Hello Server".getBytes("UTF-8"));
@ -282,7 +282,7 @@ public class RequestMappingHandlerAdapterIntegrationTests {
// SPR-13867 // SPR-13867
@Test @Test
public void handleHttpEntityWithCacheControl() throws Exception { public void handleHttpEntityWithCacheControl() throws Exception {
Class<?>[] parameterTypes = new Class<?>[] { HttpEntity.class }; Class<?>[] parameterTypes = new Class<?>[] {HttpEntity.class};
request.addHeader("Content-Type", "text/plain; charset=utf-8"); request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8")); request.setContent("Hello Server".getBytes("UTF-8"));
@ -357,27 +357,27 @@ public class RequestMappingHandlerAdapterIntegrationTests {
} }
public String handle( public String handle(
@CookieValue("cookie") int cookie, @CookieValue("cookie") int cookie,
@PathVariable("pathvar") String pathvar, @PathVariable("pathvar") String pathvar,
@RequestHeader("header") String header, @RequestHeader("header") String header,
@RequestHeader(defaultValue="#{systemProperties.systemHeader}") String systemHeader, @RequestHeader(defaultValue = "#{systemProperties.systemHeader}") String systemHeader,
@RequestHeader Map<String, Object> headerMap, @RequestHeader Map<String, Object> headerMap,
@RequestParam("dateParam") Date dateParam, @RequestParam("dateParam") Date dateParam,
@RequestParam Map<String, Object> paramMap, @RequestParam Map<String, Object> paramMap,
String paramByConvention, String paramByConvention,
@Value("#{request.contextPath}") String value, @Value("#{request.contextPath}") String value,
@ModelAttribute("modelAttr") @Valid TestBean modelAttr, @ModelAttribute("modelAttr") @Valid TestBean modelAttr,
Errors errors, Errors errors,
TestBean modelAttrByConvention, TestBean modelAttrByConvention,
Color customArg, Color customArg,
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
@SessionAttribute TestBean sessionAttribute, @SessionAttribute TestBean sessionAttribute,
@RequestAttribute TestBean requestAttribute, @RequestAttribute TestBean requestAttribute,
User user, User user,
@ModelAttribute OtherUser otherUser, @ModelAttribute OtherUser otherUser,
Model model, Model model,
UriComponentsBuilder builder) throws Exception { UriComponentsBuilder builder) throws Exception {
model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header) model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header)
.addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap) .addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap)

11
spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java

@ -549,6 +549,17 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(0, this.response.getContentLength()); assertEquals(0, this.response.getContentLength());
} }
// SPR-14005
@Test
public void doOverwriteExistingCacheControlHeaders() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.response.setHeader("Cache-Control", "no-store");
this.handler.handleRequest(this.request, this.response);
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
}
private long dateHeaderAsLong(String responseHeaderName) throws Exception { private long dateHeaderAsLong(String responseHeaderName) throws Exception {
return dateFormat.parse(this.response.getHeader(responseHeaderName)).getTime(); return dateFormat.parse(this.response.getHeader(responseHeaderName)).getTime();

Loading…
Cancel
Save