From 7c22d60fd815e1b04d79f857e382c2aa4abe5409 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Jul 2015 20:33:03 +0200 Subject: [PATCH] Streamlined WebContentGenerator API variants: checkRequest, prepareResponse, applyCacheControl, applyCacheSeconds Issue: SPR-11792 --- .../web/servlet/mvc/AbstractController.java | 7 +- .../servlet/mvc/WebContentInterceptor.java | 37 +- .../AnnotationMethodHandlerAdapter.java | 10 +- .../RequestMappingHandlerAdapter.java | 8 +- .../resource/ResourceHttpRequestHandler.java | 39 +- .../servlet/support/WebContentGenerator.java | 372 ++++++++++-------- .../web/servlet/config/MvcNamespaceTests.java | 2 - .../ResourceHandlerRegistryTests.java | 2 - .../mvc/WebContentInterceptorTests.java | 1 + .../ResourceHttpRequestHandlerTests.java | 10 +- 10 files changed, 258 insertions(+), 230 deletions(-) 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 a0eeec2a7c..46fab982b2 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 @@ -25,7 +25,7 @@ import org.springframework.web.servlet.support.WebContentGenerator; import org.springframework.web.util.WebUtils; /** - *

Convenient superclass for controller implementations, using the Template Method + * Convenient superclass for controller implementations, using the Template Method * design pattern. * *

Workflow @@ -130,7 +130,8 @@ public abstract class AbstractController extends WebContentGenerator implements throws Exception { // Delegate to WebContentGenerator for checking and preparing. - checkAndPrepare(request, response); + checkRequest(request); + prepareResponse(response); // Execute handleRequestInternal in synchronized block if required. if (this.synchronizeOnSession) { @@ -152,6 +153,6 @@ public abstract class AbstractController extends WebContentGenerator implements * @see #handleRequest */ protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) - throws Exception; + throws Exception; } 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 f56d4dc53b..038667f4b6 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 @@ -35,13 +35,14 @@ import org.springframework.web.servlet.support.WebContentGenerator; 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 {@link org.springframework.http.CacheControl}. + * Handler interceptor that checks the request and prepares the response. + * 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. * - *

All the settings supported by this interceptor can also be set on AbstractController. - * This interceptor is mainly intended for applying checks and preparations to a set of - * controllers mapped by a HandlerMapping. + *

All the settings supported by this interceptor can also be set on + * {@link AbstractController}. This interceptor is mainly intended for applying + * checks and preparations to a set of controllers mapped by a HandlerMapping. * * @author Juergen Hoeller * @author Brian Clozel @@ -58,9 +59,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle private Map cacheControlMappings = new HashMap(); + public WebContentInterceptor() { - // no restriction of HTTP methods by default, - // in particular for use with annotated controllers + // No restriction of HTTP methods by default, + // in particular for use with annotated controllers... super(false); } @@ -134,11 +136,9 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle *

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 @@ -151,7 +151,6 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle } } - /** * Set the PathMatcher implementation to use for matching URL paths * against registered URL patterns, for determining cache mappings. @@ -168,7 +167,9 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) - throws ServletException { + throws ServletException { + + checkRequest(request); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); if (logger.isDebugEnabled()) { @@ -181,19 +182,19 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle if (logger.isDebugEnabled()) { logger.debug("Applying CacheControl to [" + lookupPath + "]"); } - checkAndPrepare(request, response, cacheControl); + applyCacheControl(response, cacheControl); } else if (cacheSeconds != null) { if (logger.isDebugEnabled()) { logger.debug("Applying CacheControl to [" + lookupPath + "]"); } - checkAndPrepare(request, response, cacheSeconds); + applyCacheSeconds(response, cacheSeconds); } else { if (logger.isDebugEnabled()) { logger.debug("Applying default cache seconds to [" + lookupPath + "]"); } - checkAndPrepare(request, response); + prepareResponse(response); } return true; @@ -209,10 +210,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle * @see org.springframework.util.AntPathMatcher */ protected CacheControl lookupCacheControl(String urlPath) { - // direct match? + // Direct match? CacheControl cacheControl = this.cacheControlMappings.get(urlPath); if (cacheControl == null) { - // pattern match? + // Pattern match? for (String registeredPath : this.cacheControlMappings.keySet()) { if (this.pathMatcher.match(registeredPath, urlPath)) { cacheControl = this.cacheControlMappings.get(registeredPath); @@ -232,10 +233,10 @@ public class WebContentInterceptor extends WebContentGenerator implements Handle * @see org.springframework.util.AntPathMatcher */ protected Integer lookupCacheSeconds(String urlPath) { - // direct match? + // Direct match? Integer cacheSeconds = this.cacheMappings.get(urlPath); if (cacheSeconds == null) { - // pattern match? + // Pattern match? for (String registeredPath : this.cacheMappings.keySet()) { if (this.pathMatcher.match(registeredPath, urlPath)) { cacheSeconds = this.cacheMappings.get(registeredPath); 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 62f96c0aac..006b0784c0 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 @@ -138,8 +138,7 @@ import org.springframework.web.util.WebUtils; * @see #setMethodNameResolver * @see #setWebBindingInitializer * @see #setSessionAttributeStore - * - * @deprecated in Spring 3.2 in favor of + * @deprecated as of Spring 3.2, in favor of * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter} */ @Deprecated @@ -411,13 +410,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator } if (annotatedWithSessionAttributes) { - // Always prevent caching in case of session attribute management. - checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers); - // Prepare cached set of session attributes names. + checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } else { - // Uses configured default cacheSeconds setting. - checkAndPrepare(request, response); + checkAndPrepare(request, response, true); } // 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 b310405884..96a3c40aa3 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 @@ -705,13 +705,13 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { + checkRequest(request); + if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { - // Always prevent caching in case of session attribute management. - checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers); + applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { - // Uses configured default cacheSeconds setting. - checkAndPrepare(request, response); + prepareResponse(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 efac7c9f05..0fe1421148 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 @@ -39,9 +39,7 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; -import org.springframework.web.cors.CorsConfiguration; import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -52,6 +50,8 @@ import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestHandler; 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.support.WebContentGenerator; @@ -91,7 +91,8 @@ import org.springframework.web.servlet.support.WebContentGenerator; * @author Arjen Poutsma * @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"; @@ -171,6 +172,12 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H this.corsConfiguration = corsConfiguration; } + @Override + public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { + return this.corsConfiguration; + } + + @Override public void afterPropertiesSet() throws Exception { if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) { @@ -180,11 +187,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H initAllowedLocations(); } - @Override - public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { - return corsConfiguration; - } - /** * Look for a {@link org.springframework.web.servlet.resource.PathResourceResolver} * among the {@link #getResourceResolvers() resource resolvers} and configure @@ -207,6 +209,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H } } + /** * Processes a resource request. *

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) 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); if (resource == null) { logger.trace("No matching resource found - returning 404"); @@ -233,13 +237,16 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H return; } - // header phase + // Header phase if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) { logger.trace("Resource not modified - returning 304"); return; } - // check the resource's media type + // Apply cache settings, if any + prepareResponse(response); + + // Check the resource's media type MediaType mediaType = getMediaType(resource); if (mediaType != null) { if (logger.isTraceEnabled()) { @@ -252,7 +259,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H } } - // content phase + // Content phase if (METHOD_HEAD.equals(request.getMethod())) { setHeaders(response, resource, mediaType); 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 { - long skipped = in.skip(start); - if (skipped < start) { throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required."); } long bytesToCopy = end - start + 1; - byte buffer[] = new byte[StreamUtils.BUFFER_SIZE]; while (bytesToCopy > 0) { int bytesRead = in.read(buffer); @@ -561,8 +565,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H @Override public String toString() { - return "ResourceHttpRequestHandler [locations=" + - getLocations() + ", resolvers=" + getResourceResolvers() + "]"; + return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]"; } 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 0f8700c4a2..86fa88c677 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 @@ -16,22 +16,18 @@ package org.springframework.web.servlet.support; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.HashSet; -import java.util.Locale; import java.util.Set; -import java.util.TimeZone; import java.util.concurrent.TimeUnit; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.CacheControl; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; -import org.springframework.http.CacheControl; 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 * {@link org.springframework.web.servlet.HandlerAdapter}. * - *

Supports HTTP cache control options. The usage of corresponding - * 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. + *

Supports HTTP cache control options. The usage of corresponding HTTP + * headers can be controlled via the {@link #setCacheSeconds "cacheSeconds" + * and {@link #setCacheControl "cacheControl"} properties. * - *

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}. + *

NOTE: As of Spring 4.2, this generator's default behavior changed when + * using only {@link #setCacheSeconds)}, sending HTTP response headers that are in line + * 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 Juergen Hoeller * @author Brian Clozel * @see #setCacheSeconds + * @see #setCacheControl * @see #setRequireSession */ public abstract class WebContentGenerator extends WebApplicationObjectSupport { @@ -79,10 +78,12 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { private boolean requireSession = false; + private CacheControl cacheControl; + private int cacheSeconds = -1; /** Use HTTP 1.0 expires header? */ - private boolean useExpiresHeader = true; + private boolean useExpiresHeader = false; /** Use HTTP 1.1 cache-control header? */ private boolean useCacheControlHeader = true; @@ -92,10 +93,6 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { private boolean alwaysMustRevalidate = false; - private boolean usePreviousHttpCachingBehavior = false; - - private CacheControl cacheControl; - /** * 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 * the Cache-Control HTTP response header. - * * @since 4.2 */ - public void setCacheControl(CacheControl cacheControl) { + public final 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; + public final CacheControl getCacheControl() { + 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: + *

+ *

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

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 as of 4.2, since going forward, the HTTP 1.1 cache-control + * header will be required, with the HTTP 1.0 headers disappearing */ @Deprecated public final void setUseExpiresHeader(boolean useExpiresHeader) { this.useExpiresHeader = useExpiresHeader; - this.usePreviousHttpCachingBehavior = true; } /** * Return whether the HTTP 1.0 expires header is used. - * - * @deprecated in favor of {@link #getCacheControl}. + * @deprecated as of 4.2, in favor of {@link #getCacheControl()} */ @Deprecated 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". *

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 as of 4.2, since going forward, the HTTP 1.1 cache-control + * header will be required, with the HTTP 1.0 headers disappearing */ @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 in favor of {@link #getCacheControl}. + * @deprecated as of 4.2, in favor of {@link #getCacheControl()} */ @Deprecated 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" * when preventing caching. Default is "true". - * - * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}. + * @deprecated as of 4.2, in favor of {@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 in favor of {@link #getCacheControl}. + * @deprecated as of 4.2, in favor of {@link #getCacheControl()} */ @Deprecated 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 - * may be useful with annotated controller methods, which can - * programmatically do a lastModified calculation as described in - * {@link WebRequest#checkNotModified(long)}. Default is "false". - * - * @deprecated in favor of {@link #setCacheSeconds} or {@link #setCacheControl}. + * An option to add 'must-revalidate' to every Cache-Control header. + * This may be useful with annotated controller methods, which can + * programmatically do a last-modified calculation as described in + * {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}. + *

Default is "false". + * @deprecated as of 4.2, in favor of {@link #setCacheControl} */ @Deprecated - public void setAlwaysMustRevalidate(boolean mustRevalidate) { + public final void setAlwaysMustRevalidate(boolean mustRevalidate) { this.alwaysMustRevalidate = mustRevalidate; - this.usePreviousHttpCachingBehavior = true; } /** * Return whether 'must-revalidate' is added to every Cache-Control header. - * - * @deprecated in favor of {@link #getCacheControl}. + * @deprecated as of 4.2, in favor of {@link #getCacheControl()} */ @Deprecated - public boolean isAlwaysMustRevalidate() { - return alwaysMustRevalidate; - } - - /** - * Cache content for the given number of seconds, by writing - * cache-related HTTP headers to the response: - *

    - *
  • seconds == -1 (default value): no generation cache-related headers
  • - *
  • seconds == 0: "Cache-Control: no-store" will prevent caching
  • - *
  • seconds > 0: "Cache-Control: max-age=seconds" will ask to cache content
  • - *
- *

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; + public final boolean isAlwaysMustRevalidate() { + return this.alwaysMustRevalidate; } /** - * 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. + * Check the given request for supported methods and a required session, if any. * @param request current HTTP request - * @param response current HTTP response * @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 { // Check whether we should support the request method. 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 - * 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 + * Prepare the given response according to the settings of this generator. + * Applies the number of cache seconds specified for this generator. * @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 */ - protected final void checkAndPrepare( - HttpServletRequest request, HttpServletResponse response, CacheControl cacheControl) - throws ServletException { - - checkRequest(request); - - if (this.usePreviousHttpCachingBehavior) { - addHttp10CacheHeaders(this.cacheSeconds, response); + protected final void prepareResponse(HttpServletResponse response) { + if (this.cacheControl != null) { + applyCacheControl(response, this.cacheControl); } - else if (cacheControl != null) { - String ccValue = cacheControl.getHeaderValue(); - if (ccValue != null) { - response.setHeader(HEADER_CACHE_CONTROL, ccValue); - if (response.containsHeader(HEADER_PRAGMA)) { - response.setHeader(HEADER_PRAGMA, ""); - } + else { + applyCacheSeconds(response, this.cacheSeconds); + } + } + + /** + * 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. + *

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) { - cacheForSeconds(response, cacheSeconds, this.alwaysMustRevalidate); + cacheForSeconds(response, cacheSeconds, mustRevalidate); } else if (cacheSeconds == 0) { 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. * Tells the browser to revalidate the resource if mustRevalidate is @@ -436,38 +454,48 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { * should be cacheable for * @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 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) { + if (mustRevalidate || this.alwaysMustRevalidate) { headerValue += ", must-revalidate"; } response.setHeader(HEADER_CACHE_CONTROL, headerValue); } + if (response.containsHeader(HEADER_PRAGMA)) { + // Reset HTTP 1.0 Pragma header if present response.setHeader(HEADER_PRAGMA, ""); } } /** * Prevent the response from being cached. - * See {@code http://www.mnot.net/cache_docs}. + * Only called in HTTP 1.0 compatibility mode. + *

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) { response.setHeader(HEADER_PRAGMA, "no-cache"); + if (this.useExpiresHeader) { - // HTTP 1.0 header - response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis()); + // HTTP 1.0 Expires header + response.setDateHeader(HEADER_EXPIRES, 1L); } + if (this.useCacheControlHeader) { - // HTTP 1.1 header: "no-cache" is the standard value, - // "no-store" is necessary to prevent caching on FireFox. + // HTTP 1.1 Cache-Control header: "no-cache" is the standard value, + // "no-store" is necessary to prevent caching on Firefox. response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); if (this.useCacheControlNoStore) { response.addHeader(HEADER_CACHE_CONTROL, "no-store"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index df2311e24c..4bb8962b7e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -392,8 +392,6 @@ public class MvcNamespaceTests { ResourceHttpRequestHandler.class); assertNotNull(handler); assertEquals(3600, handler.getCacheSeconds()); - assertThat(handler.getCacheControl().getHeaderValue(), - Matchers.equalTo(CacheControl.maxAge(1, TimeUnit.HOURS).getHeaderValue())); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java index d1d0df7a44..1530dc7791 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistryTests.java @@ -90,8 +90,6 @@ public class ResourceHandlerRegistryTests { this.registration.setCachePeriod(0); assertEquals(0, getHandler("/resources/**").getCacheSeconds()); - assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(), - Matchers.equalTo(CacheControl.noStore().getHeaderValue())); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/WebContentInterceptorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/WebContentInterceptorTests.java index 273a3c64d7..a269583f9d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/WebContentInterceptorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/WebContentInterceptorTests.java @@ -137,6 +137,7 @@ public class WebContentInterceptorTests { public void http10CachingConfigAndSpecificMapping() throws Exception { WebContentInterceptor interceptor = new WebContentInterceptor(); interceptor.setCacheSeconds(0); + interceptor.setUseExpiresHeader(true); interceptor.setAlwaysMustRevalidate(true); Properties mappings = new Properties(); mappings.setProperty("**/*.cache.html", "10"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index f85c4cd9ce..7e20319b5d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -110,7 +110,7 @@ public class ResourceHttpRequestHandlerTests { @Test @SuppressWarnings("deprecation") - public void getResourcePreviousBehaviorCache() throws Exception { + public void getResourceHttp10BehaviorCache() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.handler.setCacheSeconds(3600); this.handler.setUseExpiresHeader(true); @@ -126,15 +126,17 @@ public class ResourceHttpRequestHandlerTests { @Test @SuppressWarnings("deprecation") - public void getResourcePreviousBehaviorNoCache() throws Exception { + public void getResourceHttp10BehaviorNoCache() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.handler.setCacheSeconds(0); - this.handler.setUseCacheControlNoStore(true); + this.handler.setUseExpiresHeader(true); + this.handler.setUseCacheControlNoStore(false); this.handler.setUseCacheControlHeader(true); this.handler.handleRequest(this.request, this.response); 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(this.response.containsHeader("Last-Modified")); assertEquals(dateHeaderAsLong("Last-Modified") / 1000, resourceLastModified("test/foo.css") / 1000);