From 52f311f5defb9dd7d3cf85cfba3a5dfa89cc39fb Mon Sep 17 00:00:00 2001 From: Jonathan Moore Date: Mon, 20 Dec 2010 18:30:46 +0000 Subject: [PATCH] HTTPCLIENT-975: stale-while-revalidate also yields to must-revalidate, proxy-revalidate on a shared cache, and requests with explicit freshness constraints. git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1051235 13f79535-47bb-0310-9956-ffa450edef68 --- .../impl/client/cache/CachingHttpClient.java | 4 +- .../client/cache/TestCachingHttpClient.java | 12 ++ .../client/cache/TestRFC5861Compliance.java | 136 ++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java index df334b685..4230bc62d 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java @@ -430,7 +430,9 @@ public class CachingHttpClient implements HttpClient { log.debug("Revalidating the cache entry"); try { - if (asynchRevalidator != null && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) { + if (asynchRevalidator != null + && !staleResponseNotAllowed(request, entry, now) + && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) { final HttpResponse resp = responseGenerator.generateResponse(entry); resp.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\""); diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java index d8840a177..09e0399ca 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java @@ -351,6 +351,8 @@ public class TestCachingHttpClient { getCacheEntryReturns(mockCacheEntry); cacheEntrySuitable(false); cacheEntryValidatable(true); + cacheEntryMustRevalidate(false); + cacheEntryProxyRevalidate(false); mayReturnStaleWhileRevalidating(false); revalidateCacheEntryReturns(mockBackendResponse); @@ -1944,6 +1946,16 @@ public class TestCachingHttpClient { EasyMock.anyObject())).andReturn(b); } + private void cacheEntryMustRevalidate(boolean b) { + EasyMock.expect(mockValidityPolicy.mustRevalidate(mockCacheEntry)) + .andReturn(b); + } + + private void cacheEntryProxyRevalidate(boolean b) { + EasyMock.expect(mockValidityPolicy.proxyRevalidate(mockCacheEntry)) + .andReturn(b); + } + private void mayReturnStaleWhileRevalidating(boolean b) { EasyMock.expect(mockValidityPolicy.mayReturnStaleWhileRevalidating( EasyMock.anyObject(), EasyMock.anyObject())).andReturn(b); diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java index 2906de594..986f2291a 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java @@ -300,4 +300,140 @@ public class TestRFC5861Compliance extends AbstractProtocolTest { } assertTrue(warning110Found); } + + @Test + public void testStaleWhileRevalidateYieldsToMustRevalidate() + throws Exception { + + Date now = new Date(); + Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); + + params.setAsynchronousWorkersMax(1); + impl = new CachingHttpClient(mockBackend, cache, params); + + HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + HttpResponse resp1 = HttpTestUtils.make200Response(); + resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate"); + resp1.setHeader("ETag","\"etag\""); + resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); + + backendExpectsAnyRequest().andReturn(resp1); + + HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + HttpResponse resp2 = HttpTestUtils.make200Response(); + resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate"); + resp2.setHeader("ETag","\"etag\""); + resp2.setHeader("Date", DateUtils.formatDate(now)); + + backendExpectsAnyRequest().andReturn(resp2); + + replayMocks(); + impl.execute(host, req1); + HttpResponse result = impl.execute(host, req2); + verifyMocks(); + + assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode()); + boolean warning110Found = false; + for(Header h : result.getHeaders("Warning")) { + for(WarningValue wv : WarningValue.getWarningValues(h)) { + if (wv.getWarnCode() == 110) { + warning110Found = true; + break; + } + } + } + assertFalse(warning110Found); + } + + @Test + public void testStaleWhileRevalidateYieldsToProxyRevalidateForSharedCache() + throws Exception { + + Date now = new Date(); + Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); + + params.setAsynchronousWorkersMax(1); + params.setSharedCache(true); + impl = new CachingHttpClient(mockBackend, cache, params); + + HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + HttpResponse resp1 = HttpTestUtils.make200Response(); + resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate"); + resp1.setHeader("ETag","\"etag\""); + resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); + + backendExpectsAnyRequest().andReturn(resp1); + + HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + HttpResponse resp2 = HttpTestUtils.make200Response(); + resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate"); + resp2.setHeader("ETag","\"etag\""); + resp2.setHeader("Date", DateUtils.formatDate(now)); + + backendExpectsAnyRequest().andReturn(resp2); + + replayMocks(); + impl.execute(host, req1); + HttpResponse result = impl.execute(host, req2); + verifyMocks(); + + assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode()); + boolean warning110Found = false; + for(Header h : result.getHeaders("Warning")) { + for(WarningValue wv : WarningValue.getWarningValues(h)) { + if (wv.getWarnCode() == 110) { + warning110Found = true; + break; + } + } + } + assertFalse(warning110Found); + } + + @Test + public void testStaleWhileRevalidateYieldsToExplicitFreshnessRequest() + throws Exception { + + Date now = new Date(); + Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); + + params.setAsynchronousWorkersMax(1); + params.setSharedCache(true); + impl = new CachingHttpClient(mockBackend, cache, params); + + HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + HttpResponse resp1 = HttpTestUtils.make200Response(); + resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15"); + resp1.setHeader("ETag","\"etag\""); + resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); + + backendExpectsAnyRequest().andReturn(resp1); + + HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1); + req2.setHeader("Cache-Control","min-fresh=2"); + HttpResponse resp2 = HttpTestUtils.make200Response(); + resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15"); + resp2.setHeader("ETag","\"etag\""); + resp2.setHeader("Date", DateUtils.formatDate(now)); + + backendExpectsAnyRequest().andReturn(resp2); + + replayMocks(); + impl.execute(host, req1); + HttpResponse result = impl.execute(host, req2); + verifyMocks(); + + assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode()); + boolean warning110Found = false; + for(Header h : result.getHeaders("Warning")) { + for(WarningValue wv : WarningValue.getWarningValues(h)) { + if (wv.getWarnCode() == 110) { + warning110Found = true; + break; + } + } + } + assertFalse(warning110Found); + } + }