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:
parent
5ddda40967
commit
7b6ffc39b3
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue