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:
parent
953608ec49
commit
38f32e3816
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.http;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A builder for creating "Cache-Control" HTTP response headers.
|
||||
*
|
||||
* <p>Adding Cache-Control directives to HTTP responses can significantly improve the client experience when interacting
|
||||
* with a web application. This builder creates opinionated "Cache-Control" headers with response directives only, with
|
||||
* several use cases in mind.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Caching HTTP responses with {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS)}
|
||||
* will result in {@code Cache-Control: "max-age=3600"}</li>
|
||||
* <li>Preventing cache with {@code CacheControl cc = CacheControl.noStore()}
|
||||
* will result in {@code Cache-Control: "no-store"}</li>
|
||||
* <li>Advanced cases like {@code CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform().cachePublic()}
|
||||
* will result in {@code Cache-Control: "max-age=3600, no-transform, public"}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that to be efficient, Cache-Control headers should be written along HTTP validators such as
|
||||
* "Last-Modifed" or "ETag" headers.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2">rfc7234 section 5.2.2</a>
|
||||
* @see <a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching">
|
||||
* HTTP caching - Google developers reference</a>
|
||||
* @see <a href="https://www.mnot.net/cache_docs/">Mark Nottingham's cache documentation</a>
|
||||
* @since 4.2
|
||||
*/
|
||||
public class CacheControl {
|
||||
|
||||
private boolean mustRevalidate;
|
||||
|
||||
private boolean noCache;
|
||||
|
||||
private boolean noStore;
|
||||
|
||||
private boolean noTransform;
|
||||
|
||||
private boolean cachePublic;
|
||||
|
||||
private boolean cachePrivate;
|
||||
|
||||
private boolean proxyRevalidate;
|
||||
|
||||
private long maxAge;
|
||||
|
||||
private long sMaxAge;
|
||||
|
||||
/**
|
||||
* Create a CacheControl instance with default values,
|
||||
* i.e. that will produce an empty "Cache-Control" header value.
|
||||
*/
|
||||
protected CacheControl() {
|
||||
this.mustRevalidate = false;
|
||||
this.noCache = false;
|
||||
this.noStore = false;
|
||||
this.noTransform = false;
|
||||
this.cachePublic = false;
|
||||
this.cachePrivate = false;
|
||||
this.proxyRevalidate = false;
|
||||
this.maxAge = -1;
|
||||
this.sMaxAge = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "max-age=" directive.
|
||||
*
|
||||
* <p>This directive is well suited for publicly caching resources, knowing that they won't change within
|
||||
* the configured amount of time. Additional directives can be also used, in case resources shouldn't be
|
||||
* cached ({@link #cachePrivate()}) or transformed ({@link #noTransform()}) by shared caches.
|
||||
*
|
||||
* <p>In order to prevent caches to reuse the cached response even when it has become stale
|
||||
* (i.e. the "max-age" delay is passed), the "must-revalidate" directive should be set ({@link #mustRevalidate()}
|
||||
*
|
||||
* @param maxAge the maximum time the response should be cached
|
||||
* @param unit the time unit of the {@code maxAge} argument
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.8">rfc7234 section 5.2.2.8</a>
|
||||
*/
|
||||
public static CacheControl maxAge(long maxAge, TimeUnit unit) {
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.maxAge = unit.toSeconds(maxAge);
|
||||
return cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "no-store" directive
|
||||
*
|
||||
* <p>This directive is well suited for preventing caches (browsers and proxies) to cache the content of responses.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.3">rfc7234 section 5.2.2.3</a>
|
||||
*/
|
||||
public static CacheControl noStore() {
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.noStore = true;
|
||||
return cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "no-cache" directive.
|
||||
*
|
||||
* <p>This directive is well suited for telling caches that the response can be reused only if the client
|
||||
* revalidates it with the server. This directive won't disable cache altogether and may result with
|
||||
* clients sending conditional requests (with "ETag", "If-Modified-Since" headers) and the server responding
|
||||
* with "304 - Not Modified" status.
|
||||
*
|
||||
* <p>In order to disable caching and minimize requests/responses exchanges, the {@link #noStore()} directive
|
||||
* should be used.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.2">rfc7234 section 5.2.2.2</a>
|
||||
*/
|
||||
public static CacheControl noCache() {
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.noCache = true;
|
||||
return cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an empty directive.
|
||||
*
|
||||
* <p>This is well suited for using other optional directives without "no-cache", "no-store" or "max-age".
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public static CacheControl empty() {
|
||||
CacheControl cc = new CacheControl();
|
||||
return cc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "must-revalidate" directive
|
||||
*
|
||||
* <p>This directive indicates that once it has become stale, a cache MUST NOT use the response
|
||||
* to satisfy subsequent requests without successful validation on the origin server.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.1">rfc7234 section 5.2.2.1</a>
|
||||
*/
|
||||
public CacheControl mustRevalidate() {
|
||||
this.mustRevalidate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "no-transform" directive
|
||||
*
|
||||
* <p>This directive indicates that intermediaries (caches and others) should not transform the response content.
|
||||
* This can be useful to force caches and CDNs not to automatically gzip or optimize the response content.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.4">rfc7234 section 5.2.2.4</a>
|
||||
*/
|
||||
public CacheControl noTransform() {
|
||||
this.noTransform = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "public" directive
|
||||
*
|
||||
* <p>This directive indicates that any cache MAY store the response, even if the response
|
||||
* would normally be non-cacheable or cacheable only within a private cache.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.5">rfc7234 section 5.2.2.5</a>
|
||||
*/
|
||||
public CacheControl cachePublic() {
|
||||
this.cachePublic = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "private" directive
|
||||
*
|
||||
* <p>This directive indicates that the response message is intended for a single user
|
||||
* and MUST NOT be stored by a shared cache.
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.6">rfc7234 section 5.2.2.6</a>
|
||||
*/
|
||||
public CacheControl cachePrivate() {
|
||||
this.cachePrivate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "proxy-revalidate" directive
|
||||
*
|
||||
* <p>This directive has the same meaning as the "must-revalidate" directive,
|
||||
* except that it does not apply to private caches (i.e. browsers, HTTP clients)
|
||||
*
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.7">rfc7234 section 5.2.2.7</a>
|
||||
*/
|
||||
public CacheControl proxyRevalidate() {
|
||||
this.proxyRevalidate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "s-maxage" directive
|
||||
*
|
||||
* <p>This directive indicates that, in shared caches, the maximum age specified by this directive
|
||||
* overrides the maximum age specified by other directives.
|
||||
*
|
||||
* @param sMaxAge the maximum time the response should be cached
|
||||
* @param unit the time unit of the {@code sMaxAge} argument
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2.2.9">rfc7234 section 5.2.2.9</a>
|
||||
*/
|
||||
public CacheControl sMaxAge(long sMaxAge, TimeUnit unit) {
|
||||
this.sMaxAge = unit.toSeconds(sMaxAge);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "Cache-Control" header value
|
||||
*
|
||||
* @return null if no directive was added, the header value otherwise
|
||||
*/
|
||||
public String getHeaderValue() {
|
||||
StringBuilder ccValue = new StringBuilder();
|
||||
if (this.maxAge != -1) {
|
||||
appendDirective(ccValue, "max-age=" + Long.toString(maxAge));
|
||||
}
|
||||
if (this.noCache) {
|
||||
appendDirective(ccValue, "no-cache");
|
||||
}
|
||||
if (this.noStore) {
|
||||
appendDirective(ccValue, "no-store");
|
||||
}
|
||||
if (this.mustRevalidate) {
|
||||
appendDirective(ccValue, "must-revalidate");
|
||||
}
|
||||
if (this.noTransform) {
|
||||
appendDirective(ccValue, "no-transform");
|
||||
}
|
||||
if (this.cachePublic) {
|
||||
appendDirective(ccValue, "public");
|
||||
}
|
||||
if (this.cachePrivate) {
|
||||
appendDirective(ccValue, "private");
|
||||
}
|
||||
if (this.proxyRevalidate) {
|
||||
appendDirective(ccValue, "proxy-revalidate");
|
||||
}
|
||||
if (this.sMaxAge != -1) {
|
||||
appendDirective(ccValue, "s-maxage=" + Long.toString(this.sMaxAge));
|
||||
}
|
||||
String ccHeaderValue = ccValue.toString();
|
||||
if (StringUtils.hasText(ccHeaderValue)) {
|
||||
return ccHeaderValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void appendDirective(StringBuilder b, String value) {
|
||||
if (b.length() > 0) {
|
||||
b.append(", ");
|
||||
}
|
||||
b.append(value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class CacheControlTests {
|
||||
|
||||
private static final String CACHE_CONTROL_HEADER = "Cache-Control";
|
||||
|
||||
@Test
|
||||
public void emptyCacheControl() throws Exception {
|
||||
CacheControl cc = CacheControl.empty();
|
||||
assertThat(cc.getHeaderValue(), Matchers.nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAge() throws Exception {
|
||||
CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS);
|
||||
assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAgeAndDirectives() throws Exception {
|
||||
CacheControl cc = CacheControl.maxAge(3600, TimeUnit.SECONDS).cachePublic().noTransform();
|
||||
assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, no-transform, public"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAgeAndSMaxAge() throws Exception {
|
||||
CacheControl cc = CacheControl.maxAge(1, TimeUnit.HOURS).sMaxAge(30, TimeUnit.MINUTES);
|
||||
assertThat(cc.getHeaderValue(), Matchers.equalTo("max-age=3600, s-maxage=1800"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noCachePrivate() throws Exception {
|
||||
CacheControl cc = CacheControl.noCache().cachePrivate();
|
||||
assertThat(cc.getHeaderValue(), Matchers.equalTo("no-cache, private"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noStore() throws Exception {
|
||||
CacheControl cc = CacheControl.noStore();
|
||||
assertThat(cc.getHeaderValue(), Matchers.equalTo("no-store"));
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -477,6 +477,86 @@
|
|||
<xsd:attribute name="cache-name" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="cachecontrol">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="org.springframework.web.cache.CacheControl"><![CDATA[
|
||||
Generates "Cache-Control" HTTP response headers.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="must-revalidate" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "must-revalidate" directive in the Cache-Control header.
|
||||
This indicates that caches should revalidate the cached response when it's become stale.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="no-cache" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "no-cache" directive in the Cache-Control header.
|
||||
This indicates that caches should always revalidate cached response with the server.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="no-store" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "no-store" directive in the Cache-Control header.
|
||||
This indicates that caches should never cache the response.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="no-transform" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "no-transform" directive in the Cache-Control header.
|
||||
This indicates that caches should never transform (i.e. compress, optimize) the response content.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-public" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "public" directive in the Cache-Control header.
|
||||
This indicates that any cache MAY store the response.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-private" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "private" directive in the Cache-Control header.
|
||||
This indicates that the response is intended for a single user and may not be stored by shared caches.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="proxy-revalidate" type="xsd:boolean" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "proxy-revalidate" directive in the Cache-Control header.
|
||||
This directive has the same meaning as the "must-revalidate" directive, except it only applies to shared caches.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="max-age" type="xsd:int" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "max-age" directive in the Cache-Control header.
|
||||
This indicates that the response should be cached for the given number of seconds.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="s-maxage" type="xsd:int" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Adds a "s-maxage" directive in the Cache-Control header.
|
||||
This directive has the same meaning as the "max-age" directive, except it only applies to shared caches.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:element name="resources">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation
|
||||
|
@ -487,6 +567,7 @@
|
|||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cachecontrol" type="cachecontrol" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="resource-chain" type="resource-chain" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="mapping" use="required" type="xsd:string">
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -73,6 +74,7 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
|||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
@ -202,7 +204,7 @@ public class MvcNamespaceTests {
|
|||
assertTrue(converters.size() > 0);
|
||||
for (HttpMessageConverter<?> converter : converters) {
|
||||
if (converter instanceof AbstractJackson2HttpMessageConverter) {
|
||||
ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper();
|
||||
ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter) converter).getObjectMapper();
|
||||
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
|
||||
assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
|
||||
assertFalse(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
|
||||
|
@ -233,7 +235,7 @@ public class MvcNamespaceTests {
|
|||
adapter.handle(request, response, handlerMethod);
|
||||
assertTrue(handler.recordedValidationError);
|
||||
assertEquals(LocalDate.parse("2009-10-31").toDate(), handler.date);
|
||||
assertEquals(Double.valueOf(0.9999),handler.percent);
|
||||
assertEquals(Double.valueOf(0.9999), handler.percent);
|
||||
|
||||
CompositeUriComponentsContributor uriComponentsContributor = this.appContext.getBean(
|
||||
MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME,
|
||||
|
@ -242,7 +244,7 @@ public class MvcNamespaceTests {
|
|||
assertNotNull(uriComponentsContributor);
|
||||
}
|
||||
|
||||
@Test(expected=TypeMismatchException.class)
|
||||
@Test(expected = TypeMismatchException.class)
|
||||
public void testCustomConversionService() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 13);
|
||||
|
||||
|
@ -384,10 +386,12 @@ public class MvcNamespaceTests {
|
|||
assertEquals(5, mapping.getOrder());
|
||||
assertNotNull(mapping.getUrlMap().get("/resources/**"));
|
||||
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String) mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler.class);
|
||||
assertNotNull(handler);
|
||||
assertEquals(3600, handler.getCacheSeconds());
|
||||
assertThat(handler.getCacheControl().getHeaderValue(),
|
||||
Matchers.equalTo(CacheControl.maxAge(1, TimeUnit.HOURS).getHeaderValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -397,7 +401,7 @@ public class MvcNamespaceTests {
|
|||
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
assertNotNull(mapping.getUrlMap().get("/resources/**"));
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String) mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler.class);
|
||||
assertNotNull(handler);
|
||||
|
||||
|
@ -435,10 +439,14 @@ public class MvcNamespaceTests {
|
|||
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
assertNotNull(mapping.getUrlMap().get("/resources/**"));
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String)mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler handler = appContext.getBean((String) mapping.getUrlMap().get("/resources/**"),
|
||||
ResourceHttpRequestHandler.class);
|
||||
assertNotNull(handler);
|
||||
|
||||
assertThat(handler.getCacheControl().getHeaderValue(),
|
||||
Matchers.equalTo(CacheControl.maxAge(1, TimeUnit.HOURS)
|
||||
.sMaxAge(30, TimeUnit.MINUTES).cachePublic().getHeaderValue()));
|
||||
|
||||
List<ResourceResolver> resolvers = handler.getResourceResolvers();
|
||||
assertThat(resolvers, Matchers.hasSize(3));
|
||||
assertThat(resolvers.get(0), Matchers.instanceOf(VersionResourceResolver.class));
|
||||
|
@ -762,12 +770,12 @@ public class MvcNamespaceTests {
|
|||
};
|
||||
accessor = new DirectFieldAccessor(tilesConfigurer);
|
||||
assertArrayEquals(definitions, (String[]) accessor.getPropertyValue("definitions"));
|
||||
assertTrue((boolean)accessor.getPropertyValue("checkRefresh"));
|
||||
assertTrue((boolean) accessor.getPropertyValue("checkRefresh"));
|
||||
|
||||
FreeMarkerConfigurer freeMarkerConfigurer = appContext.getBean(FreeMarkerConfigurer.class);
|
||||
assertNotNull(freeMarkerConfigurer);
|
||||
accessor = new DirectFieldAccessor(freeMarkerConfigurer);
|
||||
assertArrayEquals(new String[]{"/", "/test"}, (String[]) accessor.getPropertyValue("templateLoaderPaths"));
|
||||
assertArrayEquals(new String[] {"/", "/test"}, (String[]) accessor.getPropertyValue("templateLoaderPaths"));
|
||||
|
||||
VelocityConfigurer velocityConfigurer = appContext.getBean(VelocityConfigurer.class);
|
||||
assertNotNull(velocityConfigurer);
|
||||
|
@ -846,7 +854,7 @@ public class MvcNamespaceTests {
|
|||
}
|
||||
|
||||
|
||||
@DateTimeFormat(iso=ISO.DATE)
|
||||
@DateTimeFormat(iso = ISO.DATE)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IsoDate {
|
||||
|
@ -868,7 +876,9 @@ public class MvcNamespaceTests {
|
|||
public static class TestController {
|
||||
|
||||
private Date date;
|
||||
|
||||
private Double percent;
|
||||
|
||||
private boolean recordedValidationError;
|
||||
|
||||
@RequestMapping
|
||||
|
@ -900,7 +910,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
private static class TestBean {
|
||||
|
||||
@NotNull(groups=MyGroup.class)
|
||||
@NotNull(groups = MyGroup.class)
|
||||
private String field;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -920,7 +930,8 @@ public class MvcNamespaceTests {
|
|||
public RequestDispatcher getNamedDispatcher(String path) {
|
||||
if (path.equals("default") || path.equals("custom")) {
|
||||
return new MockRequestDispatcher("/");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -27,6 +27,7 @@ import org.springframework.cache.concurrent.ConcurrentMapCache;
|
|||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
|
@ -88,6 +89,18 @@ public class ResourceHandlerRegistryTests {
|
|||
|
||||
this.registration.setCachePeriod(0);
|
||||
assertEquals(0, getHandler("/resources/**").getCacheSeconds());
|
||||
assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(),
|
||||
Matchers.equalTo(CacheControl.noStore().getHeaderValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cacheControl() {
|
||||
assertThat(getHandler("/resources/**").getCacheControl(),
|
||||
Matchers.nullValue());
|
||||
|
||||
this.registration.setCacheControl(CacheControl.noCache().cachePrivate());
|
||||
assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(),
|
||||
Matchers.equalTo(CacheControl.noCache().cachePrivate().getHeaderValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -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.
|
||||
|
@ -53,9 +53,6 @@ public class WebContentInterceptorTests {
|
|||
|
||||
interceptor.preHandle(request, response, null);
|
||||
|
||||
List expiresHeaders = response.getHeaders("Expires");
|
||||
assertNotNull("'Expires' header not set (must be) : null", expiresHeaders);
|
||||
assertTrue("'Expires' header not set (must be) : empty", expiresHeaders.size() > 0);
|
||||
List cacheControlHeaders = response.getHeaders("Cache-Control");
|
||||
assertNotNull("'Cache-Control' header not set (must be) : null", cacheControlHeaders);
|
||||
assertTrue("'Cache-Control' header not set (must be) : empty", cacheControlHeaders.size() > 0);
|
||||
|
@ -73,17 +70,12 @@ public class WebContentInterceptorTests {
|
|||
request.setRequestURI("http://localhost:7070/example/adminhandle.vm");
|
||||
interceptor.preHandle(request, response, null);
|
||||
|
||||
List expiresHeaders = response.getHeaders("Expires");
|
||||
assertSame("'Expires' header set (must not be) : empty", 0, expiresHeaders.size());
|
||||
List cacheControlHeaders = response.getHeaders("Cache-Control");
|
||||
assertSame("'Cache-Control' header set (must not be) : empty", 0, cacheControlHeaders.size());
|
||||
assertSame("'Cache-Control' header set must be empty", 0, cacheControlHeaders.size());
|
||||
|
||||
request.setRequestURI("http://localhost:7070/example/bingo.html");
|
||||
interceptor.preHandle(request, response, null);
|
||||
|
||||
expiresHeaders = response.getHeaders("Expires");
|
||||
assertNotNull("'Expires' header not set (must be) : null", expiresHeaders);
|
||||
assertTrue("'Expires' header not set (must be) : empty", expiresHeaders.size() > 0);
|
||||
cacheControlHeaders = response.getHeaders("Cache-Control");
|
||||
assertNotNull("'Cache-Control' header not set (must be) : null", cacheControlHeaders);
|
||||
assertTrue("'Cache-Control' header not set (must be) : empty", cacheControlHeaders.size() > 0);
|
||||
|
@ -96,9 +88,6 @@ public class WebContentInterceptorTests {
|
|||
|
||||
interceptor.preHandle(request, response, null);
|
||||
|
||||
List expiresHeaders = response.getHeaders("Expires");
|
||||
assertNotNull("'Expires' header not set (must be) : null", expiresHeaders);
|
||||
assertTrue("'Expires' header not set (must be) : empty", expiresHeaders.size() > 0);
|
||||
List cacheControlHeaders = response.getHeaders("Cache-Control");
|
||||
assertNotNull("'Cache-Control' header not set (must be) : null", cacheControlHeaders);
|
||||
assertTrue("'Cache-Control' header not set (must be) : empty", cacheControlHeaders.size() > 0);
|
||||
|
|
|
@ -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.
|
||||
|
@ -118,7 +118,7 @@ public class RequestMappingHandlerAdapterTests {
|
|||
this.handlerAdapter.afterPropertiesSet();
|
||||
|
||||
this.handlerAdapter.handle(this.request, this.response, handlerMethod(handler, "handle"));
|
||||
assertEquals("no-cache", this.response.getHeader("Cache-Control"));
|
||||
assertEquals("no-store", this.response.getHeader("Cache-Control"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -16,13 +16,16 @@
|
|||
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -75,21 +78,62 @@ public class ResourceHttpRequestHandlerTests {
|
|||
|
||||
assertEquals("text/css", this.response.getContentType());
|
||||
assertEquals(17, this.response.getContentLength());
|
||||
assertTrue(headerAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
|
||||
assertEquals("max-age=3600, must-revalidate", this.response.getHeader("Cache-Control"));
|
||||
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.css"));
|
||||
assertEquals("h1 { color:red; }", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceNoCache() throws Exception {
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.setCacheSeconds(0);
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals("no-store", this.response.getHeader("Cache-Control"));
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getResourcePreviousBehaviorCache() throws Exception {
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.setCacheSeconds(3600);
|
||||
this.handler.setUseExpiresHeader(true);
|
||||
this.handler.setUseCacheControlHeader(true);
|
||||
this.handler.setAlwaysMustRevalidate(true);
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals("max-age=3600, must-revalidate", this.response.getHeader("Cache-Control"));
|
||||
assertTrue(headerAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getResourcePreviousBehaviorNoCache() throws Exception {
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.setCacheSeconds(0);
|
||||
this.handler.setUseCacheControlNoStore(true);
|
||||
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"));
|
||||
assertTrue(headerAsLong("Expires") == 1);
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceWithHtmlMediaType() throws Exception {
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.html");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals("text/html", this.response.getContentType());
|
||||
assertTrue(headerAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
|
||||
assertEquals("max-age=3600, must-revalidate", this.response.getHeader("Cache-Control"));
|
||||
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.html"));
|
||||
}
|
||||
|
@ -101,8 +145,7 @@ public class ResourceHttpRequestHandlerTests {
|
|||
|
||||
assertEquals("text/css", this.response.getContentType());
|
||||
assertEquals(17, this.response.getContentLength());
|
||||
assertTrue(headerAsLong("Expires") >= System.currentTimeMillis() - 1000 + (3600 * 1000));
|
||||
assertEquals("max-age=3600, must-revalidate", this.response.getHeader("Cache-Control"));
|
||||
assertEquals("max-age=3600", this.response.getHeader("Cache-Control"));
|
||||
assertTrue(this.response.containsHeader("Last-Modified"));
|
||||
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("testalternatepath/baz.css"));
|
||||
assertEquals("h1 { color:red; }", this.response.getContentAsString());
|
||||
|
|
|
@ -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.
|
||||
|
@ -97,9 +97,7 @@ public class MappingJackson2JsonViewTests {
|
|||
view.setUpdateContentLength(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
assertEquals("no-cache", response.getHeader("Pragma"));
|
||||
assertEquals("no-cache, no-store, max-age=0", response.getHeader("Cache-Control"));
|
||||
assertNotNull(response.getHeader("Expires"));
|
||||
assertEquals("no-store", response.getHeader("Cache-Control"));
|
||||
|
||||
assertEquals(MappingJackson2JsonView.DEFAULT_CONTENT_TYPE, response.getContentType());
|
||||
|
||||
|
@ -134,9 +132,7 @@ public class MappingJackson2JsonViewTests {
|
|||
|
||||
view.render(model, request, response);
|
||||
|
||||
assertNull(response.getHeader("Pragma"));
|
||||
assertNull(response.getHeader("Cache-Control"));
|
||||
assertNull(response.getHeader("Expires"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -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.
|
||||
|
@ -90,9 +90,7 @@ public class MappingJackson2XmlViewTests {
|
|||
view.setUpdateContentLength(true);
|
||||
view.render(model, request, response);
|
||||
|
||||
assertEquals("no-cache", response.getHeader("Pragma"));
|
||||
assertEquals("no-cache, no-store, max-age=0", response.getHeader("Cache-Control"));
|
||||
assertNotNull(response.getHeader("Expires"));
|
||||
assertEquals("no-store", response.getHeader("Cache-Control"));
|
||||
|
||||
assertEquals(MappingJackson2XmlView.DEFAULT_CONTENT_TYPE, response.getContentType());
|
||||
|
||||
|
@ -127,9 +125,7 @@ public class MappingJackson2XmlViewTests {
|
|||
|
||||
view.render(model, request, response);
|
||||
|
||||
assertNull(response.getHeader("Pragma"));
|
||||
assertNull(response.getHeader("Cache-Control"));
|
||||
assertNull(response.getHeader("Expires"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/">
|
||||
<mvc:cachecontrol max-age="3600" s-maxage="1800" cache-public="true"/>
|
||||
<mvc:resource-chain resource-cache="false" auto-registration="false">
|
||||
<mvc:resolvers>
|
||||
<mvc:version-resolver>
|
||||
|
|
Loading…
Reference in New Issue