Improve HTTP caching flexiblity

This commit improves HTTP caching defaults and flexibility in
Spring MVC.

1) Better default caching headers

The `WebContentGenerator` abstract class has been updated with
better HTTP defaults for HTTP caching, in line with current
browsers and proxies implementation (wide support of HTTP1.1, etc);
depending on the `setCacheSeconds` value:

* sends "Cache-Control: max-age=xxx" for caching responses and
do not send a "must-revalidate" value by default.
* sends "Cache-Control: no-store" or "Cache-Control: no-cache"
in order to prevent caching

Other methods used to set specific header such as
`setUseExpiresHeader` or `setAlwaysMustRevalidate` are now deprecated
in favor of `setCacheControl` for better flexibility.
Using one of the deprecated methods re-enables previous HTTP caching
behavior.

This change is applied in many Handlers, since
`WebContentGenerator` is extended by `AbstractController`,
`WebContentInterceptor`, `ResourceHttpRequestHandler` and others.

2) New CacheControl builder class

This new class brings more flexibility and allows developers
to set custom HTTP caching headers.

Several strategies are provided:

* `CacheControl.maxAge(int)` for caching responses with a
"Cache-Control: max-age=xxx" header
* `CacheControl.noStore()` prevents responses from being cached
with a "Cache-Control: no-store" header
* `CacheControl.noCache()` forces caches to revalidate the cached
response before reusing it, with a "Cache-Control: no-store" header.

From that point, it is possible to chain method calls to craft a
custom CacheControl instance:

```
CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS)
    .cachePublic().noTransform();
```

3) Configuring HTTP caching in Resource Handlers

On top of the existing ways of configuring caching mechanisms,
it is now possible to use a custom `CacheControl` to serve
resources:

```
@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS);
    registry.addResourceHandler("/resources/**)
            .addResourceLocations("classpath:/resources/")
            .setCacheControl(cc);
  }
}
```

or

```
<mvc:resources mapping="/resources/**" location="classpath:/resources/">
  <mvc:cachecontrol max-age="3600" cache-public="true"/>
</mvc:resources>
```

Issue: SPR-2779, SPR-6834, SPR-7129, SPR-9543, SPR-10464
This commit is contained in:
Brian Clozel
2015-03-11 11:19:52 +01:00
parent 953608ec49
commit 38f32e3816
21 changed files with 818 additions and 192 deletions

View File

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

View File

@@ -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.
*
* <p>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;

View File

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

View File

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

View File

@@ -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.
*
* <p>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<String, Integer> cacheMappings = new HashMap<String, Integer>();
private PathMatcher pathMatcher = new AntPathMatcher();
private Map<String, CacheControl> cacheMappings = new HashMap<String, CacheControl>();
public WebContentInterceptor() {
// no restriction of HTTP methods by default,
@@ -120,14 +123,47 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
Enumeration<?> propNames = cacheMappings.propertyNames();
while (propNames.hasMoreElements()) {
String path = (String) propNames.nextElement();
this.cacheMappings.put(path, Integer.valueOf(cacheMappings.getProperty(path)));
int cacheSeconds = Integer.valueOf(cacheMappings.getProperty(path));
if (cacheSeconds > 0) {
this.cacheMappings.put(path, CacheControl.maxAge(cacheSeconds, TimeUnit.SECONDS));
}
else if (cacheSeconds == 0) {
this.cacheMappings.put(path, CacheControl.noStore());
}
else {
this.cacheMappings.put(path, CacheControl.empty());
}
}
}
/**
* Map specific URL paths to a specific {@link org.springframework.http.CacheControl}.
* <p>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.
*
* <p>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.
* <p>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;
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -60,8 +60,8 @@ import org.springframework.web.servlet.support.WebContentGenerator;
* <p>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);

View File

@@ -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}.
*
* <p>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.
*
* <p>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<String> supportedMethods;
private Set<String> supportedMethods;
private boolean requireSession = false;
private int cacheSeconds = -1;
/** Use HTTP 1.0 expires header? */
private boolean useExpiresHeader = true;
@@ -78,10 +87,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/** Use HTTP 1.1 cache-control header value "no-store"? */
private boolean useCacheControlNoStore = true;
private int cacheSeconds = -1;
private boolean alwaysMustRevalidate = false;
private boolean usePreviousHttpCachingBehavior = false;
private CacheControl cacheControl;
/**
* Create a new WebContentGenerator which supports
@@ -151,17 +162,42 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
/**
* Set whether to use the HTTP 1.0 expires header. Default is "true".
* Set the {@link org.springframework.http.CacheControl} instance to build
* the Cache-Control HTTP response header.
*
* @since 4.2
*/
public void setCacheControl(CacheControl cacheControl) {
this.cacheControl = cacheControl;
}
/**
* Get the {@link org.springframework.http.CacheControl} instance
* that builds the Cache-Control HTTP response header.
*
* @since 4.2
*/
public CacheControl getCacheControl() {
return cacheControl;
}
/**
* Set whether to use the HTTP 1.0 expires header. Default is "false".
* <p>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".
* <p>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.
* <p>Only if this is set to 0 (no cache) or a positive value (cache for
* this many seconds) will this class generate cache headers.
* <p>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:
* <ul>
* <li>seconds == -1 (default value): no generation cache-related headers</li>
* <li>seconds == 0: "Cache-Control: no-store" will prevent caching</li>
* <li>seconds > 0: "Cache-Control: max-age=seconds" will ask to cache content</li>
* </ul>
* <p>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.
* <p>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.
}
}

View File

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