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:
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user