Provide controller level Cache-Control support

Prior to this commit, Cache-Control HTTP headers could be set using
a WebContentInterceptor and configured cache mappings.

This commit adds support for cache-related HTTP headers at the controller
method level, by returning a ResponseEntity instance:

ResponseEntity.status(HttpStatus.OK)
    .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic())
    .eTag("deadb33f8badf00d")
    .body(entity);

Also, this change now automatically checks the "ETag" and
"Last-Modified" headers in ResponseEntity, in order to respond HTTP
"304 - Not Modified" if necessary.

Issue: SPR-8550
This commit is contained in:
Brian Clozel
2015-03-11 11:19:52 +01:00
parent 38f32e3816
commit f9ce11eef8
4 changed files with 209 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,6 +59,7 @@ import org.springframework.util.ObjectUtils;
* </pre>
*
* @author Arjen Poutsma
* @author Brian Clozel
* @since 3.0.2
* @see #getStatusCode()
*/
@@ -318,6 +319,20 @@ public class ResponseEntity<T> extends HttpEntity<T> {
*/
B location(URI location);
/**
* Set the caching directives for the resource, as specified by the
* {@code Cache-Control} header.
*
* <p>A {@code CacheControl} instance can be built like
* {@code CacheControl.maxAge(3600).cachePublic().noTransform()}.
*
* @param cacheControl the instance that builds cache related HTTP response headers
* @return this builder
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">RFC-7234 Section 5.2</a>
* @since 4.2
*/
B cacheControl(CacheControl cacheControl);
/**
* Build the response entity with no body.
* @return the response entity
@@ -423,6 +438,15 @@ public class ResponseEntity<T> extends HttpEntity<T> {
return this;
}
@Override
public BodyBuilder cacheControl(CacheControl cacheControl) {
String ccValue = cacheControl.getHeaderValue();
if(ccValue != null) {
this.headers.setCacheControl(cacheControl.getHeaderValue());
}
return this;
}
@Override
public ResponseEntity<Void> build() {
return new ResponseEntity<Void>(null, this.headers, this.status);

View File

@@ -19,11 +19,14 @@ package org.springframework.http;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Matchers;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
* @author Marcel Overdijk
@@ -163,7 +166,7 @@ public class ResponseEntityTests {
}
@Test
public void headersCopy(){
public void headersCopy() {
HttpHeaders customHeaders = new HttpHeaders();
customHeaders.set("X-CustomHeader", "vale");
@@ -178,7 +181,7 @@ public class ResponseEntityTests {
}
@Test // SPR-12792
public void headersCopyWithEmptyAndNull(){
public void headersCopyWithEmptyAndNull() {
ResponseEntity<Void> responseEntityWithEmptyHeaders =
ResponseEntity.ok().headers(new HttpHeaders()).build();
ResponseEntity<Void> responseEntityWithNullHeaders =
@@ -189,4 +192,58 @@ public class ResponseEntityTests {
assertEquals(responseEntityWithEmptyHeaders.toString(), responseEntityWithNullHeaders.toString());
}
@Test
public void emptyCacheControl() {
Integer entity = new Integer(42);
ResponseEntity<Integer> responseEntity =
ResponseEntity.status(HttpStatus.OK)
.cacheControl(CacheControl.empty())
.body(entity);
assertNotNull(responseEntity);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
assertFalse(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL));
assertEquals(entity, responseEntity.getBody());
}
@Test
public void cacheControl() {
Integer entity = new Integer(42);
ResponseEntity<Integer> responseEntity =
ResponseEntity.status(HttpStatus.OK)
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePrivate().
mustRevalidate().proxyRevalidate().sMaxAge(30, TimeUnit.MINUTES))
.body(entity);
assertNotNull(responseEntity);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
assertTrue(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL));
assertEquals(entity, responseEntity.getBody());
String cacheControlHeader = responseEntity.getHeaders().getCacheControl();
assertThat(cacheControlHeader, Matchers.equalTo("max-age=3600, must-revalidate, private, proxy-revalidate, s-maxage=1800"));
}
@Test
public void cacheControlNoCache() {
Integer entity = new Integer(42);
ResponseEntity<Integer> responseEntity =
ResponseEntity.status(HttpStatus.OK)
.cacheControl(CacheControl.noStore())
.body(entity);
assertNotNull(responseEntity);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
assertTrue(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL));
assertEquals(entity, responseEntity.getBody());
String cacheControlHeader = responseEntity.getHeaders().getCacheControl();
assertThat(cacheControlHeader, Matchers.equalTo("no-store"));
}
}