diff --git a/spring-web/src/main/java/org/springframework/http/CacheControl.java b/spring-web/src/main/java/org/springframework/http/CacheControl.java new file mode 100644 index 0000000000..28279c855c --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/CacheControl.java @@ -0,0 +1,294 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http; + +import java.util.concurrent.TimeUnit; + +import org.springframework.util.StringUtils; + +/** + * A builder for creating "Cache-Control" HTTP response headers. + * + *
Adding Cache-Control directives to HTTP responses can significantly improve the client experience when interacting + * with a web application. This builder creates opinionated "Cache-Control" headers with response directives only, with + * several use cases in mind. + * + *
Note that to be efficient, Cache-Control headers should be written along HTTP validators such as + * "Last-Modifed" or "ETag" headers. + * + * @author Brian Clozel + * @see rfc7234 section 5.2.2 + * @see + * HTTP caching - Google developers reference + * @see Mark Nottingham's cache documentation + * @since 4.2 + */ +public class CacheControl { + + private boolean mustRevalidate; + + private boolean noCache; + + private boolean noStore; + + private boolean noTransform; + + private boolean cachePublic; + + private boolean cachePrivate; + + private boolean proxyRevalidate; + + private long maxAge; + + private long sMaxAge; + + /** + * Create a CacheControl instance with default values, + * i.e. that will produce an empty "Cache-Control" header value. + */ + protected CacheControl() { + this.mustRevalidate = false; + this.noCache = false; + this.noStore = false; + this.noTransform = false; + this.cachePublic = false; + this.cachePrivate = false; + this.proxyRevalidate = false; + this.maxAge = -1; + this.sMaxAge = -1; + } + + /** + * Add a "max-age=" directive. + * + *
This directive is well suited for publicly caching resources, knowing that they won't change within + * the configured amount of time. Additional directives can be also used, in case resources shouldn't be + * cached ({@link #cachePrivate()}) or transformed ({@link #noTransform()}) by shared caches. + * + *
In order to prevent caches to reuse the cached response even when it has become stale + * (i.e. the "max-age" delay is passed), the "must-revalidate" directive should be set ({@link #mustRevalidate()} + * + * @param maxAge the maximum time the response should be cached + * @param unit the time unit of the {@code maxAge} argument + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.8 + */ + public static CacheControl maxAge(long maxAge, TimeUnit unit) { + CacheControl cc = new CacheControl(); + cc.maxAge = unit.toSeconds(maxAge); + return cc; + } + + /** + * Add a "no-store" directive + * + *
This directive is well suited for preventing caches (browsers and proxies) to cache the content of responses. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.3 + */ + public static CacheControl noStore() { + CacheControl cc = new CacheControl(); + cc.noStore = true; + return cc; + } + + /** + * Add a "no-cache" directive. + * + *
This directive is well suited for telling caches that the response can be reused only if the client + * revalidates it with the server. This directive won't disable cache altogether and may result with + * clients sending conditional requests (with "ETag", "If-Modified-Since" headers) and the server responding + * with "304 - Not Modified" status. + * + *
In order to disable caching and minimize requests/responses exchanges, the {@link #noStore()} directive + * should be used. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.2 + */ + public static CacheControl noCache() { + CacheControl cc = new CacheControl(); + cc.noCache = true; + return cc; + } + + /** + * Return an empty directive. + * + *
This is well suited for using other optional directives without "no-cache", "no-store" or "max-age". + * + * @return {@code this}, to facilitate method chaining + */ + public static CacheControl empty() { + CacheControl cc = new CacheControl(); + return cc; + } + + /** + * Add a "must-revalidate" directive + * + *
This directive indicates that once it has become stale, a cache MUST NOT use the response + * to satisfy subsequent requests without successful validation on the origin server. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.1 + */ + public CacheControl mustRevalidate() { + this.mustRevalidate = true; + return this; + } + + /** + * Add a "no-transform" directive + * + *
This directive indicates that intermediaries (caches and others) should not transform the response content. + * This can be useful to force caches and CDNs not to automatically gzip or optimize the response content. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.4 + */ + public CacheControl noTransform() { + this.noTransform = true; + return this; + } + + /** + * Add a "public" directive + * + *
This directive indicates that any cache MAY store the response, even if the response + * would normally be non-cacheable or cacheable only within a private cache. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.5 + */ + public CacheControl cachePublic() { + this.cachePublic = true; + return this; + } + + /** + * Add a "private" directive + * + *
This directive indicates that the response message is intended for a single user + * and MUST NOT be stored by a shared cache. + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.6 + */ + public CacheControl cachePrivate() { + this.cachePrivate = true; + return this; + } + + /** + * Add a "proxy-revalidate" directive + * + *
This directive has the same meaning as the "must-revalidate" directive, + * except that it does not apply to private caches (i.e. browsers, HTTP clients) + * + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.7 + */ + public CacheControl proxyRevalidate() { + this.proxyRevalidate = true; + return this; + } + + /** + * Add a "s-maxage" directive + * + *
This directive indicates that, in shared caches, the maximum age specified by this directive + * overrides the maximum age specified by other directives. + * + * @param sMaxAge the maximum time the response should be cached + * @param unit the time unit of the {@code sMaxAge} argument + * @return {@code this}, to facilitate method chaining + * + * @see rfc7234 section 5.2.2.9 + */ + public CacheControl sMaxAge(long sMaxAge, TimeUnit unit) { + this.sMaxAge = unit.toSeconds(sMaxAge); + return this; + } + + /** + * Return the "Cache-Control" header value + * + * @return null if no directive was added, the header value otherwise + */ + public String getHeaderValue() { + StringBuilder ccValue = new StringBuilder(); + if (this.maxAge != -1) { + appendDirective(ccValue, "max-age=" + Long.toString(maxAge)); + } + if (this.noCache) { + appendDirective(ccValue, "no-cache"); + } + if (this.noStore) { + appendDirective(ccValue, "no-store"); + } + if (this.mustRevalidate) { + appendDirective(ccValue, "must-revalidate"); + } + if (this.noTransform) { + appendDirective(ccValue, "no-transform"); + } + if (this.cachePublic) { + appendDirective(ccValue, "public"); + } + if (this.cachePrivate) { + appendDirective(ccValue, "private"); + } + if (this.proxyRevalidate) { + appendDirective(ccValue, "proxy-revalidate"); + } + if (this.sMaxAge != -1) { + appendDirective(ccValue, "s-maxage=" + Long.toString(this.sMaxAge)); + } + String ccHeaderValue = ccValue.toString(); + if (StringUtils.hasText(ccHeaderValue)) { + return ccHeaderValue; + } + return null; + } + + private void appendDirective(StringBuilder b, String value) { + if (b.length() > 0) { + b.append(", "); + } + b.append(value); + } + +} diff --git a/spring-web/src/test/java/org/springframework/http/CacheControlTests.java b/spring-web/src/test/java/org/springframework/http/CacheControlTests.java new file mode 100644 index 0000000000..66a8a7a4b3 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/CacheControlTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http; + +import org.hamcrest.Matchers; +import org.junit.Test; + +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; + +/** + * @author Brian Clozel + */ +public class CacheControlTests { + + private static final String CACHE_CONTROL_HEADER = "Cache-Control"; + + @Test + public void emptyCacheControl() throws Exception { + CacheControl cc = CacheControl.empty(); + assertThat(cc.getHeaderValue(), Matchers.nullValue()); + } + + @Test + public void maxAge() throws Exception { + CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS); + assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600")); + } + + @Test + public void maxAgeAndDirectives() throws Exception { + CacheControl cc = CacheControl.maxAge(3600, TimeUnit.SECONDS).cachePublic().noTransform(); + assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, no-transform, public")); + } + + @Test + public void maxAgeAndSMaxAge() throws Exception { + CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).sMaxAge(30, TimeUnit.MINUTES); + assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, s-maxage=1800")); + } + + @Test + public void noCachePrivate() throws Exception { + CacheControl cc = CacheControl.noCache().cachePrivate(); + assertThat(cc.getHeaderValue(), Matchers.equalTo("no-cache, private")); + } + + @Test + public void noStore() throws Exception { + CacheControl cc = CacheControl.noStore(); + assertThat(cc.getHeaderValue(), Matchers.equalTo("no-store")); + } +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java index 26322bf7bc..9d00019646 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.web.servlet.config; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.w3c.dom.Element; @@ -34,6 +35,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.core.Ordered; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; @@ -162,6 +164,12 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser { resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds); } + Element cacheControlElement = DomUtils.getChildElementByTagName(element, "cachecontrol"); + if (cacheControlElement != null) { + CacheControl cacheControl = parseCacheControl(cacheControlElement); + resourceHandlerDef.getPropertyValues().add("cacheControl", cacheControl); + } + Element resourceChainElement = DomUtils.getChildElementByTagName(element, "resource-chain"); if (resourceChainElement != null) { parseResourceChain(resourceHandlerDef, parserContext, resourceChainElement, source); @@ -197,6 +205,38 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser { } } + private CacheControl parseCacheControl(Element element) { + CacheControl cacheControl = CacheControl.empty(); + if ("true".equals(element.getAttribute("no-cache"))) { + cacheControl = CacheControl.noCache(); + } + else if ("true".equals(element.getAttribute("no-store"))) { + cacheControl = CacheControl.noStore(); + } + else if (element.hasAttribute("max-age")) { + cacheControl = CacheControl.maxAge(Long.parseLong(element.getAttribute("max-age")), TimeUnit.SECONDS); + } + if ("true".equals(element.getAttribute("must-revalidate"))) { + cacheControl = cacheControl.mustRevalidate(); + } + if ("true".equals(element.getAttribute("no-transform"))) { + cacheControl = cacheControl.noTransform(); + } + if ("true".equals(element.getAttribute("cache-public"))) { + cacheControl = cacheControl.cachePublic(); + } + if ("true".equals(element.getAttribute("cache-private"))) { + cacheControl = cacheControl.cachePrivate(); + } + if ("true".equals(element.getAttribute("proxy-revalidate"))) { + cacheControl = cacheControl.proxyRevalidate(); + } + if (element.hasAttribute("s-maxage")) { + cacheControl = cacheControl.sMaxAge(Long.parseLong(element.getAttribute("s-maxage")), TimeUnit.SECONDS); + } + return cacheControl; + } + private void parseResourceCache(ManagedList super Object> resourceResolvers, ManagedList super Object> resourceTransformers, Element element, Object source) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java index b99730707e..b9f4b6cbf3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.springframework.cache.Cache; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.resource.PathResourceResolver; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; @@ -45,6 +46,8 @@ public class ResourceHandlerRegistration { private Integer cachePeriod; + private CacheControl cacheControl; + private ResourceChainRegistration resourceChainRegistration; @@ -69,7 +72,7 @@ public class ResourceHandlerRegistration { * {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence. * @return the same {@link ResourceHandlerRegistration} instance for chained method invocation */ - public ResourceHandlerRegistration addResourceLocations(String...resourceLocations) { + public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) { for (String location : resourceLocations) { this.locations.add(resourceLoader.getResource(location)); } @@ -88,6 +91,22 @@ public class ResourceHandlerRegistration { return this; } + /** + * Specify the {@link org.springframework.http.CacheControl} which should be used + * by the the resource handler. + * + *
Setting a custom value here will override the configuration set with {@link #setCachePeriod}. + * + * @param cacheControl the CacheControl configuration to use + * @return the same {@link ResourceHandlerRegistration} instance for chained method invocation + * + * @since 4.2 + */ + public ResourceHandlerRegistration setCacheControl(CacheControl cacheControl) { + this.cacheControl = cacheControl; + return this; + } + /** * Configure a chain of resource resolvers and transformers to use. This * can be useful for example to apply a version strategy to resource URLs. @@ -146,7 +165,10 @@ public class ResourceHandlerRegistration { handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers()); } handler.setLocations(this.locations); - if (this.cachePeriod != null) { + if (this.cacheControl != null) { + handler.setCacheControl(this.cacheControl); + } + else if (this.cachePeriod != null) { handler.setCacheSeconds(this.cachePeriod); } return handler; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index 764bec2c19..4b36302f63 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,10 +39,6 @@ import org.springframework.web.servlet.ModelAndView; */ public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { - private static final String HEADER_PRAGMA = "Pragma"; - - private static final String HEADER_EXPIRES = "Expires"; - private static final String HEADER_CACHE_CONTROL = "Cache-Control"; @@ -215,13 +211,10 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti /** * Prevents the response from being cached, through setting corresponding - * HTTP headers. See {@code http://www.mnot.net/cache_docs}. + * HTTP {@code Cache-Control: no-store} header. * @param response current HTTP response */ protected void preventCaching(HttpServletResponse response) { - response.setHeader(HEADER_PRAGMA, "no-cache"); - response.setDateHeader(HEADER_EXPIRES, 1L); - response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); response.addHeader(HEADER_CACHE_CONTROL, "no-store"); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java index 772953ffee..a0eeec2a7c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -130,7 +130,7 @@ public abstract class AbstractController extends WebContentGenerator implements throws Exception { // Delegate to WebContentGenerator for checking and preparing. - checkAndPrepare(request, response, this instanceof LastModified); + checkAndPrepare(request, response); // Execute handleRequestInternal in synchronized block if required. if (this.synchronizeOnSession) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java index f9c23f84dc..be33c5f07b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/WebContentInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.concurrent.TimeUnit; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -27,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.support.WebContentGenerator; @@ -34,7 +37,7 @@ import org.springframework.web.util.UrlPathHelper; /** * Interceptor that checks and prepares request and response. Checks for supported - * methods and a required session, and applies the specified number of cache seconds. + * methods and a required session, and applies the specified {@link org.springframework.http.CacheControl}. * See superclass bean properties for configuration options. * *
All the settings supported by this interceptor can also be set on AbstractController.
@@ -42,6 +45,7 @@ import org.springframework.web.util.UrlPathHelper;
* controllers mapped by a HandlerMapping.
*
* @author Juergen Hoeller
+ * @author Brian Clozel
* @since 27.11.2003
* @see AbstractController
*/
@@ -49,10 +53,9 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
private UrlPathHelper urlPathHelper = new UrlPathHelper();
- private Map Overrides the default cache seconds setting of this interceptor.
+ * Can specify a empty {@link org.springframework.http.CacheControl} instance
+ * to exclude a URL path from default caching.
+ *
+ * Supports direct matches, e.g. a registered "/test" matches "/test",
+ * and a various Ant-style pattern matches, e.g. a registered "/t*" matches
+ * both "/test" and "/team". For details, see the AntPathMatcher javadoc.
+ *
+ * @param cacheControl the {@code CacheControl} to use
+ * @param paths URL paths that will map to the given {@code CacheControl}
+ * @see #setCacheSeconds
+ * @see org.springframework.util.AntPathMatcher
+ * @since 4.2
+ */
+ public void addCacheMapping(CacheControl cacheControl, String... paths) {
+ for (String path : paths) {
+ this.cacheMappings.put(path, cacheControl);
+ }
+ }
+
+
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns, for determining cache mappings.
* Default is AntPathMatcher.
+ * @see #addCacheMapping
* @see #setCacheMappings
* @see org.springframework.util.AntPathMatcher
*/
@@ -146,44 +182,44 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
logger.debug("Looking up cache seconds for [" + lookupPath + "]");
}
- Integer cacheSeconds = lookupCacheSeconds(lookupPath);
- if (cacheSeconds != null) {
+ CacheControl cacheControl = lookupCacheSeconds(lookupPath);
+ if (cacheControl != null) {
if (logger.isDebugEnabled()) {
- logger.debug("Applying " + cacheSeconds + " cache seconds to [" + lookupPath + "]");
+ logger.debug("Applying CacheControl to [" + lookupPath + "]");
}
- checkAndPrepare(request, response, cacheSeconds, handler instanceof LastModified);
+ checkAndPrepare(request, response, cacheControl);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Applying default cache seconds to [" + lookupPath + "]");
}
- checkAndPrepare(request, response, handler instanceof LastModified);
+ checkAndPrepare(request, response);
}
return true;
}
/**
- * Look up a cache seconds value for the given URL path.
+ * Look up a {@link org.springframework.http.CacheControl} instance for the given URL path.
* Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* @param urlPath URL the bean is mapped to
- * @return the associated cache seconds, or {@code null} if not found
+ * @return the associated {@code CacheControl}, or {@code null} if not found
* @see org.springframework.util.AntPathMatcher
*/
- protected Integer lookupCacheSeconds(String urlPath) {
+ protected CacheControl lookupCacheSeconds(String urlPath) {
// direct match?
- Integer cacheSeconds = this.cacheMappings.get(urlPath);
- if (cacheSeconds == null) {
+ CacheControl cacheControl = this.cacheMappings.get(urlPath);
+ if (cacheControl == null) {
// pattern match?
for (String registeredPath : this.cacheMappings.keySet()) {
if (this.pathMatcher.match(registeredPath, urlPath)) {
- cacheSeconds = this.cacheMappings.get(registeredPath);
+ cacheControl = this.cacheMappings.get(registeredPath);
}
}
}
- return cacheSeconds;
+ return cacheControl;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
index 6c071af07f..a4c20452c1 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -412,12 +412,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
if (annotatedWithSessionAttributes) {
// Always prevent caching in case of session attribute management.
- checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
+ checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers);
// Prepare cached set of session attributes names.
}
else {
// Uses configured default cacheSeconds setting.
- checkAndPrepare(request, response, true);
+ checkAndPrepare(request, response);
}
// Execute invokeHandlerMethod in synchronized block if required.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
index f53d0b3fbc..b0ab919083 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -685,11 +685,11 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management.
- checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
+ checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
// Uses configured default cacheSeconds setting.
- checkAndPrepare(request, response, true);
+ checkAndPrepare(request, response);
}
// Execute invokeHandlerMethod in synchronized block if required.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
index b2e721f04d..4de612ee91 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
@@ -60,8 +60,8 @@ import org.springframework.web.servlet.support.WebContentGenerator;
* The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource}
* locations from which static resources are allowed to be served by this handler. For a given request,
* the list of locations will be consulted in order for the presence of the requested resource, and the
- * first found match will be written to the response, with {@code Expires} and {@code Cache-Control}
- * headers set as configured. The handler also properly evaluates the {@code Last-Modified} header
+ * first found match will be written to the response, with a HTTP Caching headers
+ * set as configured. The handler also properly evaluates the {@code Last-Modified} header
* (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
* overhead for resources that are already cached by the client. The use of {@code Resource} locations
* allows resource requests to easily be mapped to locations other than the web application root.
@@ -210,7 +210,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- checkAndPrepare(request, response, true);
+ checkAndPrepare(request, response);
// check whether a matching resource exists
Resource resource = getResource(request);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
index 087bcc0f85..42e1538113 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ package org.springframework.web.servlet.support;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -26,7 +28,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException;
-import org.springframework.web.context.request.WebRequest;
+import org.springframework.http.CacheControl;
import org.springframework.web.context.support.WebApplicationObjectSupport;
/**
@@ -37,11 +39,17 @@ import org.springframework.web.context.support.WebApplicationObjectSupport;
* {@link org.springframework.web.servlet.HandlerAdapter}.
*
* Supports HTTP cache control options. The usage of corresponding
- * HTTP headers can be controlled via the "useExpiresHeader",
- * "useCacheControlHeader" and "useCacheControlNoStore" properties.
+ * HTTP headers can be controlled via the "setCacheSeconds" or "setCacheControl" properties.
+ * As of 4.2, its default behavior changed when using only {@link #setCacheSeconds(int)}, sending
+ * HTTP response headers that are more in line with current browsers and proxies implementations.
+ *
+ * Reverting to the previous behavior can be easily done by using one of the nealy deprecated methods
+ * {@link #setUseExpiresHeader}, {@link #setUseCacheControlHeader}, {@link #setUseCacheControlNoStore} or
+ * {@link #setAlwaysMustRevalidate}.
*
* @author Rod Johnson
* @author Juergen Hoeller
+ * @author Brian Clozel
* @see #setCacheSeconds
* @see #setRequireSession
*/
@@ -56,7 +64,6 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/** HTTP method "POST" */
public static final String METHOD_POST = "POST";
-
private static final String HEADER_PRAGMA = "Pragma";
private static final String HEADER_EXPIRES = "Expires";
@@ -65,10 +72,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/** Set of supported HTTP methods */
- private Set Note: Cache headers will only get applied if caching is enabled
* (or explicitly prevented) for the current request.
+ *
+ * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}.
*/
+ @Deprecated
public final void setUseExpiresHeader(boolean useExpiresHeader) {
this.useExpiresHeader = useExpiresHeader;
+ this.usePreviousHttpCachingBehavior = true;
}
/**
* Return whether the HTTP 1.0 expires header is used.
*/
+ @Deprecated
public final boolean isUseExpiresHeader() {
return this.useExpiresHeader;
}
@@ -170,14 +206,19 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* Set whether to use the HTTP 1.1 cache-control header. Default is "true".
* Note: Cache headers will only get applied if caching is enabled
* (or explicitly prevented) for the current request.
+ *
+ * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}.
*/
+ @Deprecated
public final void setUseCacheControlHeader(boolean useCacheControlHeader) {
this.useCacheControlHeader = useCacheControlHeader;
+ this.usePreviousHttpCachingBehavior = true;
}
/**
* Return whether the HTTP 1.1 cache-control header is used.
*/
+ @Deprecated
public final boolean isUseCacheControlHeader() {
return this.useCacheControlHeader;
}
@@ -185,14 +226,19 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/**
* Set whether to use the HTTP 1.1 cache-control header value "no-store"
* when preventing caching. Default is "true".
+ *
+ * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}.
*/
+ @Deprecated
public final void setUseCacheControlNoStore(boolean useCacheControlNoStore) {
this.useCacheControlNoStore = useCacheControlNoStore;
+ this.usePreviousHttpCachingBehavior = true;
}
/**
* Return whether the HTTP 1.1 cache-control header value "no-store" is used.
*/
+ @Deprecated
public final boolean isUseCacheControlNoStore() {
return this.useCacheControlNoStore;
}
@@ -201,30 +247,46 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* An option to add 'must-revalidate' to every Cache-Control header. This
* may be useful with annotated controller methods, which can
* programmatically do a lastModified calculation as described in
- * {@link WebRequest#checkNotModified(long)}. Default is "false",
- * effectively relying on whether the handler implements
- * {@link org.springframework.web.servlet.mvc.LastModified} or not.
+ * {@link WebRequest#checkNotModified(long)}. Default is "false".
+ *
+ * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}.
*/
+ @Deprecated
public void setAlwaysMustRevalidate(boolean mustRevalidate) {
this.alwaysMustRevalidate = mustRevalidate;
+ this.usePreviousHttpCachingBehavior = true;
}
/**
* Return whether 'must-revalidate' is added to every Cache-Control header.
*/
+ @Deprecated
public boolean isAlwaysMustRevalidate() {
return alwaysMustRevalidate;
}
/**
- * Cache content for the given number of seconds. Default is -1,
- * indicating no generation of cache-related headers.
- * Only if this is set to 0 (no cache) or a positive value (cache for
- * this many seconds) will this class generate cache headers.
- * The headers can be overwritten by subclasses, before content is generated.
+ * Cache content for the given number of seconds, by writing
+ * cache-related HTTP headers to the response:
+ * For more specific needs, a custom {@link org.springframework.http.CacheControl} should be used.
+ *
+ * @see #setCacheControl
*/
public final void setCacheSeconds(int seconds) {
this.cacheSeconds = seconds;
+ if (!this.usePreviousHttpCachingBehavior) {
+ if (cacheSeconds > 0) {
+ this.cacheControl = CacheControl.maxAge(seconds, TimeUnit.SECONDS);
+ }
+ else if (cacheSeconds == 0) {
+ this.cacheControl = CacheControl.noStore();
+ }
+ }
}
/**
@@ -241,29 +303,45 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* and applies the number of cache seconds specified for this generator.
* @param request current HTTP request
* @param response current HTTP response
- * @param lastModified if the mapped handler provides Last-Modified support
* @throws ServletException if the request cannot be handled because a check failed
*/
protected final void checkAndPrepare(
- HttpServletRequest request, HttpServletResponse response, boolean lastModified)
+ HttpServletRequest request, HttpServletResponse response)
throws ServletException {
- checkAndPrepare(request, response, this.cacheSeconds, lastModified);
+ checkAndPrepare(request, response, this.cacheControl);
+ }
+
+ protected final void checkAndPrepare(
+ HttpServletRequest request, HttpServletResponse response, int cacheSeconds) throws ServletException {
+
+ CacheControl cControl;
+ if (cacheSeconds > 0) {
+ cControl = CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS);
+ }
+ else if (cacheSeconds == 0) {
+ cControl = CacheControl.noStore();
+ }
+ else {
+ cControl = CacheControl.empty();
+ }
+ checkAndPrepare(request, response, cControl);
}
/**
* Check and prepare the given request and response according to the settings
- * of this generator. Checks for supported methods and a required session,
- * and applies the given number of cache seconds.
+ * of this generator. Checks for supported methods and a required session
+ * specified for this generator. Also applies the {@link org.springframework.http.CacheControl}
+ * given as a parameter.
* @param request current HTTP request
* @param response current HTTP response
- * @param cacheSeconds positive number of seconds into the future that the
- * response should be cacheable for, 0 to prevent caching
- * @param lastModified if the mapped handler provides Last-Modified support
+ * @param cacheControl the {@link org.springframework.http.CacheControl} to use
* @throws ServletException if the request cannot be handled because a check failed
+ *
+ * @since 4.2
*/
protected final void checkAndPrepare(
- HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
+ HttpServletRequest request, HttpServletResponse response, CacheControl cacheControl)
throws ServletException {
// Check whether we should support the request method.
@@ -280,9 +358,49 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
}
- // Do declarative cache control.
- // Revalidate if the controller supports last-modified.
- applyCacheSeconds(response, cacheSeconds, lastModified);
+ if (this.usePreviousHttpCachingBehavior) {
+ addHttp10CacheHeaders(response);
+ }
+ else if (cacheControl != null) {
+ String ccValue = cacheControl.getHeaderValue();
+ if (ccValue != null) {
+ response.setHeader(HEADER_CACHE_CONTROL, ccValue);
+ }
+ }
+ }
+
+ protected void addHttp10CacheHeaders(HttpServletResponse response) {
+ if (this.cacheSeconds > 0) {
+ cacheForSeconds(response, this.cacheSeconds, this.alwaysMustRevalidate);
+ }
+ else if (this.cacheSeconds == 0) {
+ preventCaching(response);
+ }
+ }
+
+ /**
+ * Set HTTP headers to allow caching for the given number of seconds.
+ * Tells the browser to revalidate the resource if mustRevalidate is
+ * {@code true}.
+ * @param response the current HTTP response
+ * @param seconds number of seconds into the future that the response
+ * should be cacheable for
+ * @param mustRevalidate whether the client should revalidate the resource
+ * (typically only necessary for controllers with last-modified support)
+ */
+ protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
+ if (this.useExpiresHeader) {
+ // HTTP 1.0 header
+ response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
+ }
+ if (this.useCacheControlHeader) {
+ // HTTP 1.1 header
+ String headerValue = "max-age=" + seconds;
+ if (mustRevalidate) {
+ headerValue += ", must-revalidate";
+ }
+ response.setHeader(HEADER_CACHE_CONTROL, headerValue);
+ }
}
/**
@@ -305,77 +423,4 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
}
- /**
- * Set HTTP headers to allow caching for the given number of seconds.
- * Does not tell the browser to revalidate the resource.
- * @param response current HTTP response
- * @param seconds number of seconds into the future that the response
- * should be cacheable for
- * @see #cacheForSeconds(javax.servlet.http.HttpServletResponse, int, boolean)
- */
- protected final void cacheForSeconds(HttpServletResponse response, int seconds) {
- cacheForSeconds(response, seconds, false);
- }
-
- /**
- * Set HTTP headers to allow caching for the given number of seconds.
- * Tells the browser to revalidate the resource if mustRevalidate is
- * {@code true}.
- * @param response the current HTTP response
- * @param seconds number of seconds into the future that the response
- * should be cacheable for
- * @param mustRevalidate whether the client should revalidate the resource
- * (typically only necessary for controllers with last-modified support)
- */
- protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
- if (this.useExpiresHeader) {
- // HTTP 1.0 header
- response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
- }
- if (this.useCacheControlHeader) {
- // HTTP 1.1 header
- String headerValue = "max-age=" + seconds;
- if (mustRevalidate || this.alwaysMustRevalidate) {
- headerValue += ", must-revalidate";
- }
- response.setHeader(HEADER_CACHE_CONTROL, headerValue);
- }
- }
-
- /**
- * Apply the given cache seconds and generate corresponding HTTP headers,
- * i.e. allow caching for the given number of seconds in case of a positive
- * value, prevent caching if given a 0 value, do nothing else.
- * Does not tell the browser to revalidate the resource.
- * @param response current HTTP response
- * @param seconds positive number of seconds into the future that the
- * response should be cacheable for, 0 to prevent caching
- * @see #cacheForSeconds(javax.servlet.http.HttpServletResponse, int, boolean)
- */
- protected final void applyCacheSeconds(HttpServletResponse response, int seconds) {
- applyCacheSeconds(response, seconds, false);
- }
-
- /**
- * Apply the given cache seconds and generate respective HTTP headers.
- * That is, allow caching for the given number of seconds in the
- * case of a positive value, prevent caching if given a 0 value, else
- * do nothing (i.e. leave caching to the client).
- * @param response the current HTTP response
- * @param seconds the (positive) number of seconds into the future that
- * the response should be cacheable for; 0 to prevent caching; and
- * a negative value to leave caching to the client.
- * @param mustRevalidate whether the client should revalidate the resource
- * (typically only necessary for controllers with last-modified support)
- */
- protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
- if (seconds > 0) {
- cacheForSeconds(response, seconds, mustRevalidate);
- }
- else if (seconds == 0) {
- preventCaching(response);
- }
- // Leave caching to the client otherwise.
- }
-
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
index 3ce9ecdfb1..f326c718c8 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -150,9 +150,7 @@ public abstract class AbstractJackson2View extends AbstractView {
setResponseContentType(request, response);
response.setCharacterEncoding(this.encoding.getJavaName());
if (this.disableCaching) {
- response.addHeader("Pragma", "no-cache");
- response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
- response.addDateHeader("Expires", 1L);
+ response.addHeader("Cache-Control", "no-store");
}
}
diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd
index 7f615bdd62..39570b69ed 100644
--- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd
+++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd
@@ -477,6 +477,86 @@
+ *
+ *