diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java index 7d8e5ed8c..84d270910 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java @@ -201,7 +201,8 @@ class ResponseCachingPolicy { return false; } - if (request.getRequestLine().getUri().contains("?") && !isExplicitlyCacheable(response)) { + if (request.getRequestLine().getUri().contains("?") && + (!isExplicitlyCacheable(response) || from1_0Origin(response))) { log.debug("Response was not cacheable."); return false; } @@ -220,6 +221,21 @@ class ResponseCachingPolicy { return isResponseCacheable(method, response); } + private boolean from1_0Origin(HttpResponse response) { + Header via = response.getFirstHeader("Via"); + if (via != null) { + for(HeaderElement elt : via.getElements()) { + String proto = elt.toString().split("\\s")[0]; + if (proto.contains("/")) { + return proto.equals("HTTP/1.0"); + } else { + return proto.equals("1.0"); + } + } + } + return HttpVersion.HTTP_1_0.equals(response.getProtocolVersion()); + } + private boolean requestProtocolGreaterThanAccepted(HttpRequest req) { return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0; } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java index 35e14fab2..b3c05cd12 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java @@ -59,6 +59,7 @@ import org.junit.Test; */ public class TestProtocolRecommendations extends AbstractProtocolTest { + private Date tenSecondsFromNow; private Date now; private Date tenSecondsAgo; @@ -68,6 +69,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest { super.setUp(); now = new Date(); tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); + tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L); } /* "identity: The default (identity) encoding; the use of no @@ -1078,4 +1080,66 @@ public class TestProtocolRecommendations extends AbstractProtocolTest { impl.execute(host, req3); verifyMocks(); } + + /* + * "This specifically means that responses from HTTP/1.0 servers for such + * URIs [those containing a '?' in the rel_path part] SHOULD NOT be taken + * from a cache." + * + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9 + */ + @Test + public void responseToGetWithQueryFrom1_0OriginIsNotCached() + throws Exception { + HttpRequest req1 = new HttpGet("http://foo.example.com/bar?baz=quux"); + HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + resp1.setEntity(HttpTestUtils.makeBody(200)); + resp1.setHeader("Content-Length","200"); + resp1.setHeader("Date", formatDate(now)); + resp1.setHeader("Expires", formatDate(tenSecondsFromNow)); + + backendExpectsAnyRequest().andReturn(resp1); + + HttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux"); + HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + resp2.setEntity(HttpTestUtils.makeBody(200)); + resp2.setHeader("Content-Length","200"); + resp2.setHeader("Date", formatDate(now)); + + backendExpectsAnyRequest().andReturn(resp2); + + replayMocks(); + impl.execute(host, req1); + impl.execute(host, req2); + verifyMocks(); + } + + @Test + public void responseToGetWithQueryFrom1_0OriginVia1_1ProxyIsNotCached() + throws Exception { + HttpRequest req1 = new HttpGet("http://foo.example.com/bar?baz=quux"); + HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); + resp1.setEntity(HttpTestUtils.makeBody(200)); + resp1.setHeader("Content-Length","200"); + resp1.setHeader("Date", formatDate(now)); + resp1.setHeader("Expires", formatDate(tenSecondsFromNow)); + resp1.setHeader("Via","1.0 someproxy"); + + backendExpectsAnyRequest().andReturn(resp1); + + HttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux"); + HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + resp2.setEntity(HttpTestUtils.makeBody(200)); + resp2.setHeader("Content-Length","200"); + resp2.setHeader("Date", formatDate(now)); + resp2.setHeader("Via","1.0 someproxy"); + + backendExpectsAnyRequest().andReturn(resp2); + + replayMocks(); + impl.execute(host, req1); + impl.execute(host, req2); + verifyMocks(); + } + } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java index 6ea0dcae1..2c604691c 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java @@ -32,8 +32,9 @@ import java.util.Random; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.HttpVersion; import org.apache.http.ProtocolVersion; -import org.apache.http.impl.cookie.DateUtils; +import static org.apache.http.impl.cookie.DateUtils.formatDate; import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; @@ -57,7 +58,7 @@ public class TestResponseCachingPolicy { request = new BasicHttpRequest("GET","/",HTTP_1_1); response = new BasicHttpResponse( new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); - response.setHeader("Date", DateUtils.formatDate(new Date())); + response.setHeader("Date", formatDate(new Date())); response.setHeader("Content-Length", "0"); } @@ -163,7 +164,7 @@ public class TestResponseCachingPolicy { public void testNon206WithExplicitExpiresIsCacheable() { int status = getRandomStatus(); response.setStatusCode(status); - response.setHeader("Expires", DateUtils.formatDate(new Date())); + response.setHeader("Expires", formatDate(new Date())); Assert.assertTrue(policy.isResponseCacheable("GET", response)); } @@ -275,7 +276,7 @@ public class TestResponseCachingPolicy { response = new BasicHttpResponse( new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); - response.setHeader("Date", DateUtils.formatDate(new Date())); + response.setHeader("Date", formatDate(new Date())); response.addHeader("Cache-Control", "no-transform"); response.setHeader("Content-Length", "0"); @@ -333,8 +334,8 @@ public class TestResponseCachingPolicy { public void testResponsesWithMultipleDateHeadersAreNotCacheable() { Date now = new Date(); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); - response.addHeader("Date", DateUtils.formatDate(now)); - response.addHeader("Date", DateUtils.formatDate(sixSecondsAgo)); + response.addHeader("Date", formatDate(now)); + response.addHeader("Date", formatDate(sixSecondsAgo)); Assert.assertFalse(policy.isResponseCacheable("GET", response)); } @@ -348,8 +349,8 @@ public class TestResponseCachingPolicy { public void testResponsesWithMultipleExpiresHeadersAreNotCacheable() { Date now = new Date(); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); - response.addHeader("Expires", DateUtils.formatDate(now)); - response.addHeader("Expires", DateUtils.formatDate(sixSecondsAgo)); + response.addHeader("Expires", formatDate(now)); + response.addHeader("Expires", formatDate(sixSecondsAgo)); Assert.assertFalse(policy.isResponseCacheable("GET", response)); } @@ -380,10 +381,69 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() { request = new BasicHttpRequest("GET", "/foo?s=bar"); - response.setHeader("Expires", DateUtils.formatDate(new Date())); + response.setHeader("Expires", formatDate(new Date())); Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheable() { + request = new BasicHttpRequest("GET", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + + @Test + 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)); + } + + @Test + public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheable() { + request = new BasicHttpRequest("GET", "/foo?s=bar"); + response.setHeader("Via", "1.0 someproxy"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + + @Test + public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheableEvenWithExpires() { + 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", "1.0 someproxy"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + + @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"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + + @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)); + response.setHeader("Via", "1.1 someproxy"); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + private int getRandomStatus() { int rnd = (new Random()).nextInt(acceptableCodes.length);