Responses from HTTP/1.0 origins to requests containing query parameters

SHOULD NOT be taken from a cache.

http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1057715 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2011-01-11 16:31:06 +00:00
parent 020d303ec5
commit e95a80b7a2
3 changed files with 150 additions and 10 deletions

View File

@ -201,7 +201,8 @@ public boolean isResponseCacheable(HttpRequest request, HttpResponse response) {
return false; 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."); log.debug("Response was not cacheable.");
return false; return false;
} }
@ -220,6 +221,21 @@ public boolean isResponseCacheable(HttpRequest request, HttpResponse response) {
return isResponseCacheable(method, response); 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) { private boolean requestProtocolGreaterThanAccepted(HttpRequest req) {
return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0; return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0;
} }

View File

@ -59,6 +59,7 @@
*/ */
public class TestProtocolRecommendations extends AbstractProtocolTest { public class TestProtocolRecommendations extends AbstractProtocolTest {
private Date tenSecondsFromNow;
private Date now; private Date now;
private Date tenSecondsAgo; private Date tenSecondsAgo;
@ -68,6 +69,7 @@ public void setUp() {
super.setUp(); super.setUp();
now = new Date(); now = new Date();
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
} }
/* "identity: The default (identity) encoding; the use of no /* "identity: The default (identity) encoding; the use of no
@ -1078,4 +1080,66 @@ public void cachedEntryShouldNotBeUsedIfMoreRecentMentionInContentLocation()
impl.execute(host, req3); impl.execute(host, req3);
verifyMocks(); 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();
}
} }

View File

@ -32,8 +32,9 @@
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion; 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.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine; import org.apache.http.message.BasicStatusLine;
@ -57,7 +58,7 @@ public void setUp() throws Exception {
request = new BasicHttpRequest("GET","/",HTTP_1_1); request = new BasicHttpRequest("GET","/",HTTP_1_1);
response = new BasicHttpResponse( response = new BasicHttpResponse(
new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); 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"); response.setHeader("Content-Length", "0");
} }
@ -163,7 +164,7 @@ public void testPlain307ResponseCodeIsNotCacheable() {
public void testNon206WithExplicitExpiresIsCacheable() { public void testNon206WithExplicitExpiresIsCacheable() {
int status = getRandomStatus(); int status = getRandomStatus();
response.setStatusCode(status); response.setStatusCode(status);
response.setHeader("Expires", DateUtils.formatDate(new Date())); response.setHeader("Expires", formatDate(new Date()));
Assert.assertTrue(policy.isResponseCacheable("GET", response)); Assert.assertTrue(policy.isResponseCacheable("GET", response));
} }
@ -275,7 +276,7 @@ public void testIsGetWithAnyCacheControlCacheable() {
response = new BasicHttpResponse( response = new BasicHttpResponse(
new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); 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.addHeader("Cache-Control", "no-transform");
response.setHeader("Content-Length", "0"); response.setHeader("Content-Length", "0");
@ -333,8 +334,8 @@ public void testResponsesWithMultipleAgeHeadersAreNotCacheable() {
public void testResponsesWithMultipleDateHeadersAreNotCacheable() { public void testResponsesWithMultipleDateHeadersAreNotCacheable() {
Date now = new Date(); Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
response.addHeader("Date", DateUtils.formatDate(now)); response.addHeader("Date", formatDate(now));
response.addHeader("Date", DateUtils.formatDate(sixSecondsAgo)); response.addHeader("Date", formatDate(sixSecondsAgo));
Assert.assertFalse(policy.isResponseCacheable("GET", response)); Assert.assertFalse(policy.isResponseCacheable("GET", response));
} }
@ -348,8 +349,8 @@ public void testResponsesWithMalformedDateHeadersAreNotCacheable() {
public void testResponsesWithMultipleExpiresHeadersAreNotCacheable() { public void testResponsesWithMultipleExpiresHeadersAreNotCacheable() {
Date now = new Date(); Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
response.addHeader("Expires", DateUtils.formatDate(now)); response.addHeader("Expires", formatDate(now));
response.addHeader("Expires", DateUtils.formatDate(sixSecondsAgo)); response.addHeader("Expires", formatDate(sixSecondsAgo));
Assert.assertFalse(policy.isResponseCacheable("GET", response)); Assert.assertFalse(policy.isResponseCacheable("GET", response));
} }
@ -380,7 +381,66 @@ public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheable
@Test @Test
public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() { public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() {
request = new BasicHttpRequest("GET", "/foo?s=bar"); 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)); Assert.assertTrue(policy.isResponseCacheable(request, response));
} }