Streamlined WebContentGenerator API variants: checkRequest, prepareResponse, applyCacheControl, applyCacheSeconds

Issue: SPR-11792
This commit is contained in:
Juergen Hoeller 2015-07-21 20:33:03 +02:00
parent b277c081e7
commit 7c22d60fd8
10 changed files with 258 additions and 230 deletions

View File

@ -25,7 +25,7 @@ import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
/** /**
* <p>Convenient superclass for controller implementations, using the Template Method * Convenient superclass for controller implementations, using the Template Method
* design pattern. * design pattern.
* *
* <p><b><a name="workflow">Workflow * <p><b><a name="workflow">Workflow
@ -130,7 +130,8 @@ public abstract class AbstractController extends WebContentGenerator implements
throws Exception { throws Exception {
// Delegate to WebContentGenerator for checking and preparing. // Delegate to WebContentGenerator for checking and preparing.
checkAndPrepare(request, response); checkRequest(request);
prepareResponse(response);
// Execute handleRequestInternal in synchronized block if required. // Execute handleRequestInternal in synchronized block if required.
if (this.synchronizeOnSession) { if (this.synchronizeOnSession) {
@ -152,6 +153,6 @@ public abstract class AbstractController extends WebContentGenerator implements
* @see #handleRequest * @see #handleRequest
*/ */
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception; throws Exception;
} }

View File

@ -35,13 +35,14 @@ import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
/** /**
* Interceptor that checks and prepares request and response. Checks for supported * Handler interceptor that checks the request and prepares the response.
* methods and a required session, and applies the specified {@link org.springframework.http.CacheControl}. * Checks for supported methods and a required session, and applies the
* specified {@link org.springframework.http.CacheControl} builder.
* See superclass bean properties for configuration options. * See superclass bean properties for configuration options.
* *
* <p>All the settings supported by this interceptor can also be set on AbstractController. * <p>All the settings supported by this interceptor can also be set on
* This interceptor is mainly intended for applying checks and preparations to a set of * {@link AbstractController}. This interceptor is mainly intended for applying
* controllers mapped by a HandlerMapping. * checks and preparations to a set of controllers mapped by a HandlerMapping.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Brian Clozel * @author Brian Clozel
@ -58,9 +59,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
private Map<String, CacheControl> cacheControlMappings = new HashMap<String, CacheControl>(); private Map<String, CacheControl> cacheControlMappings = new HashMap<String, CacheControl>();
public WebContentInterceptor() { public WebContentInterceptor() {
// no restriction of HTTP methods by default, // No restriction of HTTP methods by default,
// in particular for use with annotated controllers // in particular for use with annotated controllers...
super(false); super(false);
} }
@ -134,11 +136,9 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
* <p>Overrides the default cache seconds setting of this interceptor. * <p>Overrides the default cache seconds setting of this interceptor.
* Can specify a empty {@link org.springframework.http.CacheControl} instance * Can specify a empty {@link org.springframework.http.CacheControl} instance
* to exclude a URL path from default caching. * to exclude a URL path from default caching.
*
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and a various Ant-style pattern matches, e.g. a registered "/t*" matches * and a various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher javadoc. * both "/test" and "/team". For details, see the AntPathMatcher javadoc.
*
* @param cacheControl the {@code CacheControl} to use * @param cacheControl the {@code CacheControl} to use
* @param paths URL paths that will map to the given {@code CacheControl} * @param paths URL paths that will map to the given {@code CacheControl}
* @see #setCacheSeconds * @see #setCacheSeconds
@ -151,7 +151,6 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
} }
} }
/** /**
* Set the PathMatcher implementation to use for matching URL paths * Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns, for determining cache mappings. * against registered URL patterns, for determining cache mappings.
@ -168,7 +167,9 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException { throws ServletException {
checkRequest(request);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -181,19 +182,19 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Applying CacheControl to [" + lookupPath + "]"); logger.debug("Applying CacheControl to [" + lookupPath + "]");
} }
checkAndPrepare(request, response, cacheControl); applyCacheControl(response, cacheControl);
} }
else if (cacheSeconds != null) { else if (cacheSeconds != null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Applying CacheControl to [" + lookupPath + "]"); logger.debug("Applying CacheControl to [" + lookupPath + "]");
} }
checkAndPrepare(request, response, cacheSeconds); applyCacheSeconds(response, cacheSeconds);
} }
else { else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Applying default cache seconds to [" + lookupPath + "]"); logger.debug("Applying default cache seconds to [" + lookupPath + "]");
} }
checkAndPrepare(request, response); prepareResponse(response);
} }
return true; return true;
@ -209,10 +210,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
* @see org.springframework.util.AntPathMatcher * @see org.springframework.util.AntPathMatcher
*/ */
protected CacheControl lookupCacheControl(String urlPath) { protected CacheControl lookupCacheControl(String urlPath) {
// direct match? // Direct match?
CacheControl cacheControl = this.cacheControlMappings.get(urlPath); CacheControl cacheControl = this.cacheControlMappings.get(urlPath);
if (cacheControl == null) { if (cacheControl == null) {
// pattern match? // Pattern match?
for (String registeredPath : this.cacheControlMappings.keySet()) { for (String registeredPath : this.cacheControlMappings.keySet()) {
if (this.pathMatcher.match(registeredPath, urlPath)) { if (this.pathMatcher.match(registeredPath, urlPath)) {
cacheControl = this.cacheControlMappings.get(registeredPath); cacheControl = this.cacheControlMappings.get(registeredPath);
@ -232,10 +233,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle
* @see org.springframework.util.AntPathMatcher * @see org.springframework.util.AntPathMatcher
*/ */
protected Integer lookupCacheSeconds(String urlPath) { protected Integer lookupCacheSeconds(String urlPath) {
// direct match? // Direct match?
Integer cacheSeconds = this.cacheMappings.get(urlPath); Integer cacheSeconds = this.cacheMappings.get(urlPath);
if (cacheSeconds == null) { if (cacheSeconds == null) {
// pattern match? // Pattern match?
for (String registeredPath : this.cacheMappings.keySet()) { for (String registeredPath : this.cacheMappings.keySet()) {
if (this.pathMatcher.match(registeredPath, urlPath)) { if (this.pathMatcher.match(registeredPath, urlPath)) {
cacheSeconds = this.cacheMappings.get(registeredPath); cacheSeconds = this.cacheMappings.get(registeredPath);

View File

@ -138,8 +138,7 @@ import org.springframework.web.util.WebUtils;
* @see #setMethodNameResolver * @see #setMethodNameResolver
* @see #setWebBindingInitializer * @see #setWebBindingInitializer
* @see #setSessionAttributeStore * @see #setSessionAttributeStore
* * @deprecated as of Spring 3.2, in favor of
* @deprecated in Spring 3.2 in favor of
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter} * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter}
*/ */
@Deprecated @Deprecated
@ -411,13 +410,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
} }
if (annotatedWithSessionAttributes) { 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 { else {
// Uses configured default cacheSeconds setting. checkAndPrepare(request, response, true);
checkAndPrepare(request, response);
} }
// Execute invokeHandlerMethod in synchronized block if required. // Execute invokeHandlerMethod in synchronized block if required.

View File

@ -705,13 +705,13 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
protected ModelAndView handleInternal(HttpServletRequest request, protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
checkRequest(request);
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
// Always prevent caching in case of session attribute management. applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers);
} }
else { else {
// Uses configured default cacheSeconds setting. prepareResponse(response);
checkAndPrepare(request, response);
} }
// Execute invokeHandlerMethod in synchronized block if required. // Execute invokeHandlerMethod in synchronized block if required.

View File

@ -39,9 +39,7 @@ import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange; import org.springframework.http.HttpRange;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -52,6 +50,8 @@ import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator; import org.springframework.web.servlet.support.WebContentGenerator;
@ -91,7 +91,8 @@ import org.springframework.web.servlet.support.WebContentGenerator;
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 3.0.4 * @since 3.0.4
*/ */
public class ResourceHttpRequestHandler extends WebContentGenerator implements HttpRequestHandler, InitializingBean, CorsConfigurationSource { public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, InitializingBean, CorsConfigurationSource {
private static final String CONTENT_ENCODING = "Content-Encoding"; private static final String CONTENT_ENCODING = "Content-Encoding";
@ -171,6 +172,12 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
this.corsConfiguration = corsConfiguration; this.corsConfiguration = corsConfiguration;
} }
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.corsConfiguration;
}
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) { if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) {
@ -180,11 +187,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
initAllowedLocations(); initAllowedLocations();
} }
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return corsConfiguration;
}
/** /**
* Look for a {@link org.springframework.web.servlet.resource.PathResourceResolver} * Look for a {@link org.springframework.web.servlet.resource.PathResourceResolver}
* among the {@link #getResourceResolvers() resource resolvers} and configure * among the {@link #getResourceResolvers() resource resolvers} and configure
@ -207,6 +209,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
} }
} }
/** /**
* Processes a resource request. * Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations. * <p>Checks for the existence of the requested resource in the configured list of locations.
@ -223,9 +226,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
public void handleRequest(HttpServletRequest request, HttpServletResponse response) public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
checkAndPrepare(request, response); // Supported methods and required session
checkRequest(request);
// check whether a matching resource exists // Check whether a matching resource exists
Resource resource = getResource(request); Resource resource = getResource(request);
if (resource == null) { if (resource == null) {
logger.trace("No matching resource found - returning 404"); logger.trace("No matching resource found - returning 404");
@ -233,13 +237,16 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
return; return;
} }
// header phase // Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) { if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304"); logger.trace("Resource not modified - returning 304");
return; return;
} }
// check the resource's media type // Apply cache settings, if any
prepareResponse(response);
// Check the resource's media type
MediaType mediaType = getMediaType(resource); MediaType mediaType = getMediaType(resource);
if (mediaType != null) { if (mediaType != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
@ -252,7 +259,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
} }
} }
// content phase // Content phase
if (METHOD_HEAD.equals(request.getMethod())) { if (METHOD_HEAD.equals(request.getMethod())) {
setHeaders(response, resource, mediaType); setHeaders(response, resource, mediaType);
logger.trace("HEAD request - skipping content"); logger.trace("HEAD request - skipping content");
@ -532,15 +539,12 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
} }
private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException { private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
long skipped = in.skip(start); long skipped = in.skip(start);
if (skipped < start) { if (skipped < start) {
throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required."); throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
} }
long bytesToCopy = end - start + 1; long bytesToCopy = end - start + 1;
byte buffer[] = new byte[StreamUtils.BUFFER_SIZE]; byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
while (bytesToCopy > 0) { while (bytesToCopy > 0) {
int bytesRead = in.read(buffer); int bytesRead = in.read(buffer);
@ -561,8 +565,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
@Override @Override
public String toString() { public String toString() {
return "ResourceHttpRequestHandler [locations=" + return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
getLocations() + ", resolvers=" + getResourceResolvers() + "]";
} }

View File

@ -16,22 +16,18 @@
package org.springframework.web.servlet.support; package org.springframework.web.servlet.support;
import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.HttpSessionRequiredException;
import org.springframework.http.CacheControl;
import org.springframework.web.context.support.WebApplicationObjectSupport; import org.springframework.web.context.support.WebApplicationObjectSupport;
/** /**
@ -41,19 +37,22 @@ import org.springframework.web.context.support.WebApplicationObjectSupport;
* Can also be used for custom handlers that have their own * Can also be used for custom handlers that have their own
* {@link org.springframework.web.servlet.HandlerAdapter}. * {@link org.springframework.web.servlet.HandlerAdapter}.
* *
* <p>Supports HTTP cache control options. The usage of corresponding * <p>Supports HTTP cache control options. The usage of corresponding HTTP
* HTTP headers can be controlled via the "setCacheSeconds" or "setCacheControl" properties. * headers can be controlled via the {@link #setCacheSeconds "cacheSeconds"
* As of 4.2, its default behavior changed when using only {@link #setCacheSeconds(int)}, sending * and {@link #setCacheControl "cacheControl"} properties.
* 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 * <p><b>NOTE:</b> As of Spring 4.2, this generator's default behavior changed when
* {@link #setUseExpiresHeader}, {@link #setUseCacheControlHeader}, {@link #setUseCacheControlNoStore} or * using only {@link #setCacheSeconds)}, sending HTTP response headers that are in line
* {@link #setAlwaysMustRevalidate}. * with current browsers and proxies implementations (i.e. no HTTP 1.0 headers anymore)
* Reverting to the previous behavior can be easily done by using one of the newly
* deprecated methods {@link #setUseExpiresHeader}, {@link #setUseCacheControlHeader},
* {@link #setUseCacheControlNoStore} or {@link #setAlwaysMustRevalidate}.
* *
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Brian Clozel * @author Brian Clozel
* @see #setCacheSeconds * @see #setCacheSeconds
* @see #setCacheControl
* @see #setRequireSession * @see #setRequireSession
*/ */
public abstract class WebContentGenerator extends WebApplicationObjectSupport { public abstract class WebContentGenerator extends WebApplicationObjectSupport {
@ -79,10 +78,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
private boolean requireSession = false; private boolean requireSession = false;
private CacheControl cacheControl;
private int cacheSeconds = -1; private int cacheSeconds = -1;
/** Use HTTP 1.0 expires header? */ /** Use HTTP 1.0 expires header? */
private boolean useExpiresHeader = true; private boolean useExpiresHeader = false;
/** Use HTTP 1.1 cache-control header? */ /** Use HTTP 1.1 cache-control header? */
private boolean useCacheControlHeader = true; private boolean useCacheControlHeader = true;
@ -92,10 +93,6 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
private boolean alwaysMustRevalidate = false; private boolean alwaysMustRevalidate = false;
private boolean usePreviousHttpCachingBehavior = false;
private CacheControl cacheControl;
/** /**
* Create a new WebContentGenerator which supports * Create a new WebContentGenerator which supports
@ -167,40 +164,60 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/** /**
* Set the {@link org.springframework.http.CacheControl} instance to build * Set the {@link org.springframework.http.CacheControl} instance to build
* the Cache-Control HTTP response header. * the Cache-Control HTTP response header.
*
* @since 4.2 * @since 4.2
*/ */
public void setCacheControl(CacheControl cacheControl) { public final void setCacheControl(CacheControl cacheControl) {
this.cacheControl = cacheControl; this.cacheControl = cacheControl;
} }
/** /**
* Get the {@link org.springframework.http.CacheControl} instance * Get the {@link org.springframework.http.CacheControl} instance
* that builds the Cache-Control HTTP response header. * that builds the Cache-Control HTTP response header.
*
* @since 4.2 * @since 4.2
*/ */
public CacheControl getCacheControl() { public final CacheControl getCacheControl() {
return cacheControl; return this.cacheControl;
} }
/** /**
* Set whether to use the HTTP 1.0 expires header. Default is "false". * 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;
}
/**
* Return the number of seconds that content is cached.
*/
public final int getCacheSeconds() {
return this.cacheSeconds;
}
/**
* Set whether to use the HTTP 1.0 expires header. Default is "false",
* as of 4.2.
* <p>Note: Cache headers will only get applied if caching is enabled * <p>Note: Cache headers will only get applied if caching is enabled
* (or explicitly prevented) for the current request. * (or explicitly prevented) for the current request.
* * @deprecated as of 4.2, since going forward, the HTTP 1.1 cache-control
* @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}. * header will be required, with the HTTP 1.0 headers disappearing
*/ */
@Deprecated @Deprecated
public final void setUseExpiresHeader(boolean useExpiresHeader) { public final void setUseExpiresHeader(boolean useExpiresHeader) {
this.useExpiresHeader = useExpiresHeader; this.useExpiresHeader = useExpiresHeader;
this.usePreviousHttpCachingBehavior = true;
} }
/** /**
* Return whether the HTTP 1.0 expires header is used. * Return whether the HTTP 1.0 expires header is used.
* * @deprecated as of 4.2, in favor of {@link #getCacheControl()}
* @deprecated in favor of {@link #getCacheControl}.
*/ */
@Deprecated @Deprecated
public final boolean isUseExpiresHeader() { public final boolean isUseExpiresHeader() {
@ -211,19 +228,17 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* Set whether to use the HTTP 1.1 cache-control header. Default is "true". * 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 * <p>Note: Cache headers will only get applied if caching is enabled
* (or explicitly prevented) for the current request. * (or explicitly prevented) for the current request.
* * @deprecated as of 4.2, since going forward, the HTTP 1.1 cache-control
* @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}. * header will be required, with the HTTP 1.0 headers disappearing
*/ */
@Deprecated @Deprecated
public final void setUseCacheControlHeader(boolean useCacheControlHeader) { public final void setUseCacheControlHeader(boolean useCacheControlHeader) {
this.useCacheControlHeader = useCacheControlHeader; this.useCacheControlHeader = useCacheControlHeader;
this.usePreviousHttpCachingBehavior = true;
} }
/** /**
* Return whether the HTTP 1.1 cache-control header is used. * Return whether the HTTP 1.1 cache-control header is used.
* * @deprecated as of 4.2, in favor of {@link #getCacheControl()}
* @deprecated in favor of {@link #getCacheControl}.
*/ */
@Deprecated @Deprecated
public final boolean isUseCacheControlHeader() { public final boolean isUseCacheControlHeader() {
@ -233,19 +248,16 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
/** /**
* Set whether to use the HTTP 1.1 cache-control header value "no-store" * Set whether to use the HTTP 1.1 cache-control header value "no-store"
* when preventing caching. Default is "true". * when preventing caching. Default is "true".
* * @deprecated as of 4.2, in favor of {@link #setCacheControl}
* @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}.
*/ */
@Deprecated @Deprecated
public final void setUseCacheControlNoStore(boolean useCacheControlNoStore) { public final void setUseCacheControlNoStore(boolean useCacheControlNoStore) {
this.useCacheControlNoStore = useCacheControlNoStore; this.useCacheControlNoStore = useCacheControlNoStore;
this.usePreviousHttpCachingBehavior = true;
} }
/** /**
* Return whether the HTTP 1.1 cache-control header value "no-store" is used. * Return whether the HTTP 1.1 cache-control header value "no-store" is used.
* * @deprecated as of 4.2, in favor of {@link #getCacheControl()}
* @deprecated in favor of {@link #getCacheControl}.
*/ */
@Deprecated @Deprecated
public final boolean isUseCacheControlNoStore() { public final boolean isUseCacheControlNoStore() {
@ -253,125 +265,34 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
} }
/** /**
* An option to add 'must-revalidate' to every Cache-Control header. This * An option to add 'must-revalidate' to every Cache-Control header.
* may be useful with annotated controller methods, which can * This may be useful with annotated controller methods, which can
* programmatically do a lastModified calculation as described in * programmatically do a last-modified calculation as described in
* {@link WebRequest#checkNotModified(long)}. Default is "false". * {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}.
* * <p>Default is "false".
* @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}. * @deprecated as of 4.2, in favor of {@link #setCacheControl}
*/ */
@Deprecated @Deprecated
public void setAlwaysMustRevalidate(boolean mustRevalidate) { public final void setAlwaysMustRevalidate(boolean mustRevalidate) {
this.alwaysMustRevalidate = mustRevalidate; this.alwaysMustRevalidate = mustRevalidate;
this.usePreviousHttpCachingBehavior = true;
} }
/** /**
* Return whether 'must-revalidate' is added to every Cache-Control header. * Return whether 'must-revalidate' is added to every Cache-Control header.
* * @deprecated as of 4.2, in favor of {@link #getCacheControl()}
* @deprecated in favor of {@link #getCacheControl}.
*/ */
@Deprecated @Deprecated
public boolean isAlwaysMustRevalidate() { public final boolean isAlwaysMustRevalidate() {
return alwaysMustRevalidate; return this.alwaysMustRevalidate;
}
/**
* 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();
}
}
}
/**
* Return the number of seconds that content is cached.
*/
public final int getCacheSeconds() {
return this.cacheSeconds;
} }
/** /**
* Check and prepare the given request and response according to the settings * Check the given request for supported methods and a required session, if any.
* of this generator. Checks for supported methods and a required session,
* and applies the number of cache seconds specified for this generator.
* @param request current HTTP request * @param request current HTTP request
* @param response current HTTP response
* @throws ServletException if the request cannot be handled because a check failed * @throws ServletException if the request cannot be handled because a check failed
* @since 4.2
*/ */
protected final void checkAndPrepare(
HttpServletRequest request, HttpServletResponse response)
throws ServletException {
checkAndPrepare(request, response, this.cacheControl);
}
/**
* * 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 number of cache seconds specified for this generator.
* @param request current HTTP request
* @param response current HTTP response
* @param lastModified whether the underlying handler writes "Last-Modified" headers; in that case,
* a "Cache-Control: must-revalidate" directive is set in the response.
* @throws ServletException if the request cannot be handled because a check failed
* @deprecated in favor of {@link #checkAndPrepare(HttpServletRequest, HttpServletResponse, CacheControl)}.
*/
@Deprecated
protected final void checkAndPrepare(
HttpServletRequest request, HttpServletResponse response, boolean lastModified)
throws ServletException {
if (lastModified) {
checkAndPrepare(request, response, this.cacheControl.mustRevalidate());
}
else {
checkAndPrepare(request, response);
}
}
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();
}
checkRequest(request);
if (this.usePreviousHttpCachingBehavior) {
addHttp10CacheHeaders(cacheSeconds, response);
}
else {
String ccValue = cControl.getHeaderValue();
if (ccValue != null) {
response.setHeader(HEADER_CACHE_CONTROL, ccValue);
}
}
}
protected final void checkRequest(HttpServletRequest request) throws ServletException { protected final void checkRequest(HttpServletRequest request) throws ServletException {
// Check whether we should support the request method. // Check whether we should support the request method.
String method = request.getMethod(); String method = request.getMethod();
@ -387,46 +308,143 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
} }
/** /**
* Check and prepare the given request and response according to the settings * Prepare the given response according to the settings of this generator.
* of this generator. Checks for supported methods and a required session * Applies the number of cache seconds specified for this generator.
* 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 response current HTTP response
* @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 * @since 4.2
*/ */
protected final void checkAndPrepare( protected final void prepareResponse(HttpServletResponse response) {
HttpServletRequest request, HttpServletResponse response, CacheControl cacheControl) if (this.cacheControl != null) {
throws ServletException { applyCacheControl(response, this.cacheControl);
checkRequest(request);
if (this.usePreviousHttpCachingBehavior) {
addHttp10CacheHeaders(this.cacheSeconds, response);
} }
else if (cacheControl != null) { else {
String ccValue = cacheControl.getHeaderValue(); applyCacheSeconds(response, this.cacheSeconds);
if (ccValue != null) { }
response.setHeader(HEADER_CACHE_CONTROL, ccValue); }
if (response.containsHeader(HEADER_PRAGMA)) {
response.setHeader(HEADER_PRAGMA, ""); /**
} * Set the HTTP Cache-Control header according to the given settings.
* @param response current HTTP response
* @param cacheControl the pre-configured cache control settings
* @since 4.2
*/
protected final void applyCacheControl(HttpServletResponse response, CacheControl cacheControl) {
String ccValue = cacheControl.getHeaderValue();
if (ccValue != null) {
// Set computed HTTP 1.1 Cache-Control header
response.setHeader(HEADER_CACHE_CONTROL, ccValue);
if (response.containsHeader(HEADER_PRAGMA)) {
// Reset HTTP 1.0 Pragma header if present
response.setHeader(HEADER_PRAGMA, "");
} }
} }
} }
protected final void addHttp10CacheHeaders(int cacheSeconds, HttpServletResponse response) { /**
* 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 cacheSeconds positive number of seconds into the future that the
* response should be cacheable for, 0 to prevent caching
*/
@SuppressWarnings("deprecation")
protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds) {
if (this.useExpiresHeader || !this.useCacheControlHeader) {
// Deprecated HTTP 1.0 cache behavior, as in previous Spring versions
if (cacheSeconds > 0) {
cacheForSeconds(response, cacheSeconds);
}
else if (cacheSeconds == 0) {
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 {
cControl = CacheControl.empty();
}
applyCacheControl(response, cControl);
}
}
/**
* @see #checkRequest(HttpServletRequest)
* @see #prepareResponse(HttpServletResponse)
* @deprecated as of 4.2, since the {@code lastModified} flag is effectively ignored,
* with a must-revalidate header only generated if explicitly configured
*/
@Deprecated
protected final void checkAndPrepare(
HttpServletRequest request, HttpServletResponse response, boolean lastModified) throws ServletException {
checkRequest(request);
prepareResponse(response);
}
/**
* @see #checkRequest(HttpServletRequest)
* @see #applyCacheSeconds(HttpServletResponse, int)
* @deprecated as of 4.2, since the {@code lastModified} flag is effectively ignored,
* with a must-revalidate header only generated if explicitly configured
*/
@Deprecated
protected final void checkAndPrepare(
HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
throws ServletException {
checkRequest(request);
applyCacheSeconds(response, cacheSeconds);
}
/**
* 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 cacheSeconds 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)
* @deprecated as of 4.2, in favor of {@link #applyCacheControl}
*/
@Deprecated
protected final void applyCacheSeconds(HttpServletResponse response, int cacheSeconds, boolean mustRevalidate) {
if (cacheSeconds > 0) { if (cacheSeconds > 0) {
cacheForSeconds(response, cacheSeconds, this.alwaysMustRevalidate); cacheForSeconds(response, cacheSeconds, mustRevalidate);
} }
else if (cacheSeconds == 0) { else if (cacheSeconds == 0) {
preventCaching(response); preventCaching(response);
} }
} }
/**
* 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
* @deprecated as of 4.2, in favor of {@link #applyCacheControl}
*/
@Deprecated
protected final void cacheForSeconds(HttpServletResponse response, int seconds) {
cacheForSeconds(response, seconds, false);
}
/** /**
* Set HTTP headers to allow caching for the given number of seconds. * Set HTTP headers to allow caching for the given number of seconds.
* Tells the browser to revalidate the resource if mustRevalidate is * Tells the browser to revalidate the resource if mustRevalidate is
@ -436,38 +454,48 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
* should be cacheable for * should be cacheable for
* @param mustRevalidate whether the client should revalidate the resource * @param mustRevalidate whether the client should revalidate the resource
* (typically only necessary for controllers with last-modified support) * (typically only necessary for controllers with last-modified support)
* @deprecated as of 4.2, in favor of {@link #applyCacheControl}
*/ */
@Deprecated
protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) { protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
if (this.useExpiresHeader) { if (this.useExpiresHeader) {
// HTTP 1.0 header // HTTP 1.0 header
response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L); response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
} }
if (this.useCacheControlHeader) { if (this.useCacheControlHeader) {
// HTTP 1.1 header // HTTP 1.1 header
String headerValue = "max-age=" + seconds; String headerValue = "max-age=" + seconds;
if (mustRevalidate) { if (mustRevalidate || this.alwaysMustRevalidate) {
headerValue += ", must-revalidate"; headerValue += ", must-revalidate";
} }
response.setHeader(HEADER_CACHE_CONTROL, headerValue); response.setHeader(HEADER_CACHE_CONTROL, headerValue);
} }
if (response.containsHeader(HEADER_PRAGMA)) { if (response.containsHeader(HEADER_PRAGMA)) {
// Reset HTTP 1.0 Pragma header if present
response.setHeader(HEADER_PRAGMA, ""); response.setHeader(HEADER_PRAGMA, "");
} }
} }
/** /**
* Prevent the response from being cached. * Prevent the response from being cached.
* See {@code http://www.mnot.net/cache_docs}. * Only called in HTTP 1.0 compatibility mode.
* <p>See {@code http://www.mnot.net/cache_docs}.
* @deprecated as of 4.2, in favor of {@link #applyCacheControl}
*/ */
@Deprecated
protected final void preventCaching(HttpServletResponse response) { protected final void preventCaching(HttpServletResponse response) {
response.setHeader(HEADER_PRAGMA, "no-cache"); response.setHeader(HEADER_PRAGMA, "no-cache");
if (this.useExpiresHeader) { if (this.useExpiresHeader) {
// HTTP 1.0 header // HTTP 1.0 Expires header
response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis()); response.setDateHeader(HEADER_EXPIRES, 1L);
} }
if (this.useCacheControlHeader) { if (this.useCacheControlHeader) {
// HTTP 1.1 header: "no-cache" is the standard value, // HTTP 1.1 Cache-Control header: "no-cache" is the standard value,
// "no-store" is necessary to prevent caching on FireFox. // "no-store" is necessary to prevent caching on Firefox.
response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
if (this.useCacheControlNoStore) { if (this.useCacheControlNoStore) {
response.addHeader(HEADER_CACHE_CONTROL, "no-store"); response.addHeader(HEADER_CACHE_CONTROL, "no-store");

View File

@ -392,8 +392,6 @@ public class MvcNamespaceTests {
ResourceHttpRequestHandler.class); ResourceHttpRequestHandler.class);
assertNotNull(handler); assertNotNull(handler);
assertEquals(3600, handler.getCacheSeconds()); assertEquals(3600, handler.getCacheSeconds());
assertThat(handler.getCacheControl().getHeaderValue(),
Matchers.equalTo(CacheControl.maxAge(1, TimeUnit.HOURS).getHeaderValue()));
} }
@Test @Test

View File

@ -90,8 +90,6 @@ public class ResourceHandlerRegistryTests {
this.registration.setCachePeriod(0); this.registration.setCachePeriod(0);
assertEquals(0, getHandler("/resources/**").getCacheSeconds()); assertEquals(0, getHandler("/resources/**").getCacheSeconds());
assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(),
Matchers.equalTo(CacheControl.noStore().getHeaderValue()));
} }
@Test @Test

View File

@ -137,6 +137,7 @@ public class WebContentInterceptorTests {
public void http10CachingConfigAndSpecificMapping() throws Exception { public void http10CachingConfigAndSpecificMapping() throws Exception {
WebContentInterceptor interceptor = new WebContentInterceptor(); WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.setCacheSeconds(0); interceptor.setCacheSeconds(0);
interceptor.setUseExpiresHeader(true);
interceptor.setAlwaysMustRevalidate(true); interceptor.setAlwaysMustRevalidate(true);
Properties mappings = new Properties(); Properties mappings = new Properties();
mappings.setProperty("**/*.cache.html", "10"); mappings.setProperty("**/*.cache.html", "10");

View File

@ -110,7 +110,7 @@ public class ResourceHttpRequestHandlerTests {
@Test @Test
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void getResourcePreviousBehaviorCache() throws Exception { public void getResourceHttp10BehaviorCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(3600); this.handler.setCacheSeconds(3600);
this.handler.setUseExpiresHeader(true); this.handler.setUseExpiresHeader(true);
@ -126,15 +126,17 @@ public class ResourceHttpRequestHandlerTests {
@Test @Test
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void getResourcePreviousBehaviorNoCache() throws Exception { public void getResourceHttp10BehaviorNoCache() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
this.handler.setCacheSeconds(0); this.handler.setCacheSeconds(0);
this.handler.setUseCacheControlNoStore(true); this.handler.setUseExpiresHeader(true);
this.handler.setUseCacheControlNoStore(false);
this.handler.setUseCacheControlHeader(true); this.handler.setUseCacheControlHeader(true);
this.handler.handleRequest(this.request, this.response); this.handler.handleRequest(this.request, this.response);
assertEquals("no-cache", this.response.getHeader("Pragma")); assertEquals("no-cache", this.response.getHeader("Pragma"));
assertThat(this.response.getHeaderValues("Cache-Control"), Matchers.contains("no-cache", "no-store")); assertThat(this.response.getHeaderValues("Cache-Control"), Matchers.iterableWithSize(1));
assertEquals("no-cache", this.response.getHeader("Cache-Control"));
assertTrue(dateHeaderAsLong("Expires") <= System.currentTimeMillis()); assertTrue(dateHeaderAsLong("Expires") <= System.currentTimeMillis());
assertTrue(this.response.containsHeader("Last-Modified")); assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(dateHeaderAsLong("Last-Modified") / 1000, resourceLastModified("test/foo.css") / 1000); assertEquals(dateHeaderAsLong("Last-Modified") / 1000, resourceLastModified("test/foo.css") / 1000);