Implementation fix and acceptance tests for protocol recommendation:

"Many HTTP/1.0 cache implementations will treat an Expires value that
is less than or equal to the response Date value as being equivalent
to the Cache-Control response directive "no-cache". If an HTTP/1.1
cache receives such a response, and the response does not include a
Cache-Control header field, it SHOULD consider the response to be non-
cacheable in order to retain compatibility with HTTP/1.0 servers."

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3

Also had to update a few other test cases that incidentally ran afoul
of this recommendation.


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1058247 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2011-01-12 17:35:29 +00:00
parent 5ddda40967
commit 7b6ffc39b3
3 changed files with 112 additions and 9 deletions

View File

@ -26,6 +26,8 @@
*/
package org.apache.http.impl.client.cache;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
@ -196,6 +198,7 @@ class ResponseCachingPolicy {
log.debug("Response was not cacheable.");
return false;
}
String[] uncacheableRequestDirectives = { "no-store" };
if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) {
return false;
@ -207,6 +210,10 @@ class ResponseCachingPolicy {
return false;
}
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) {
return false;
}
if (sharedCache) {
Header[] authNHeaders = request.getHeaders("Authorization");
if (authNHeaders != null && authNHeaders.length > 0) {
@ -221,6 +228,21 @@ class ResponseCachingPolicy {
return isResponseCacheable(method, response);
}
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(
HttpResponse response) {
if (response.getFirstHeader("Cache-Control") != null) return false;
Header expiresHdr = response.getFirstHeader("Expires");
Header dateHdr = response.getFirstHeader("Date");
if (expiresHdr == null || dateHdr == null) return false;
try {
Date expires = DateUtils.parseDate(expiresHdr.getValue());
Date date = DateUtils.parseDate(dateHdr.getValue());
return expires.equals(date) || expires.before(date);
} catch (DateParseException dpe) {
return false;
}
}
private boolean from1_0Origin(HttpResponse response) {
Header via = response.getFirstHeader("Via");
if (via != null) {

View File

@ -717,6 +717,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires",formatDate(oneSecondAgo));
resp1.setHeader("Cache-Control", "public");
backendExpectsAnyRequest().andReturn(resp1);
@ -1268,4 +1269,68 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
/*
* "Many HTTP/1.0 cache implementations will treat an Expires value
* that is less than or equal to the response Date value as being
* equivalent to the Cache-Control response directive 'no-cache'.
* If an HTTP/1.1 cache receives such a response, and the response
* does not include a Cache-Control header field, it SHOULD consider
* the response to be non-cacheable in order to retain compatibility
* with HTTP/1.0 servers."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
*/
@Test
public void expiresEqualToDateWithNoCacheControlIsNotCacheable()
throws Exception {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(now));
resp1.removeHeaders("Cache-Control");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
@Test
public void expiresPriorToDateWithNoCacheControlIsNotCacheable()
throws Exception {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(tenSecondsAgo));
resp1.removeHeaders("Cache-Control");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\"");
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
}

View File

@ -51,9 +51,16 @@ public class TestResponseCachingPolicy {
private int[] acceptableCodes = new int[] { HttpStatus.SC_OK,
HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, HttpStatus.SC_MULTIPLE_CHOICES,
HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_GONE };
private Date now;
private Date tenSecondsFromNow;
private Date sixSecondsAgo;
@Before
public void setUp() throws Exception {
now = new Date();
sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
policy = new ResponseCachingPolicy(0, true);
request = new BasicHttpRequest("GET","/",HTTP_1_1);
response = new BasicHttpResponse(
@ -332,8 +339,6 @@ public class TestResponseCachingPolicy {
@Test
public void testResponsesWithMultipleDateHeadersAreNotCacheable() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
response.addHeader("Date", formatDate(now));
response.addHeader("Date", formatDate(sixSecondsAgo));
Assert.assertFalse(policy.isResponseCacheable("GET", response));
@ -381,7 +386,8 @@ public class TestResponseCachingPolicy {
@Test
public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
response.setHeader("Expires", formatDate(new Date()));
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(tenSecondsFromNow));
Assert.assertTrue(policy.isResponseCacheable(request, response));
}
@ -396,8 +402,6 @@ public class TestResponseCachingPolicy {
public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithExpires() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
Date now = new Date();
Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(tenSecondsFromNow));
Assert.assertFalse(policy.isResponseCacheable(request, response));
@ -424,8 +428,6 @@ public class TestResponseCachingPolicy {
@Test
public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreNotCacheableEvenWithExpires() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
Date now = new Date();
Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(tenSecondsFromNow));
response.setHeader("Via", "HTTP/1.0 someproxy");
@ -435,8 +437,6 @@ public class TestResponseCachingPolicy {
@Test
public void getsWithQueryParametersFrom1_1OriginsVia1_0ProxiesAreCacheableWithExpires() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
Date now = new Date();
Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(tenSecondsFromNow));
@ -444,6 +444,22 @@ public class TestResponseCachingPolicy {
Assert.assertTrue(policy.isResponseCacheable(request, response));
}
@Test
public void notCacheableIfExpiresEqualsDateAndNoCacheControl() {
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(now));
response.removeHeaders("Cache-Control");
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
@Test
public void notCacheableIfExpiresPrecedesDateAndNoCacheControl() {
response.setHeader("Date", formatDate(now));
response.setHeader("Expires", formatDate(sixSecondsAgo));
response.removeHeaders("Cache-Control");
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
private int getRandomStatus() {
int rnd = (new Random()).nextInt(acceptableCodes.length);