HTTPCLIENT-1035: committed validating acceptance test and wired in new

cache invalidation call. Also cleaned up some unit tests in
TestCachingHttpClient: static imports of EasyMock methods, as well as
removing unit tests that exercised some protected methods, especially
where they are already covered by acceptance tests against a public
interface (unit testing protected methods leads to brittle tests). Also
rewrite some of the unit tests in TestCachingHttpClient to depend on
fewer mocks (often, just a mock backend HttpClient and mock cache
storage).


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1052894 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2010-12-26 15:10:39 +00:00
parent 7b99aca6ab
commit 856c1c28ad
9 changed files with 478 additions and 469 deletions

View File

@ -84,6 +84,10 @@ class BasicHttpCache implements HttpCache {
storage.removeEntry(uri);
}
public void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request, HttpResponse response) {
cacheInvalidator.flushInvalidatedCacheEntries(host, request, response);
}
void storeInCache(
HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
if (entry.hasVariants()) {

View File

@ -189,6 +189,8 @@ class CacheInvalidator {
*/
public void flushInvalidatedCacheEntries(HttpHost host,
HttpRequest request, HttpResponse response) {
int status = response.getStatusLine().getStatusCode();
if (status < 200 || status > 299) return;
URL reqURL = getAbsoluteURL(cacheKeyGenerator.getURI(host, request));
if (reqURL == null) return;
URL canonURL = getContentLocationURL(reqURL, response);

View File

@ -840,6 +840,7 @@ public class CachingHttpClient implements HttpClient {
responseCompliance.ensureProtocolCompliance(request, backendResponse);
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
if (cacheable &&
!alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
try {

View File

@ -57,6 +57,15 @@ interface HttpCache {
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request)
throws IOException;
/** Clear any entries that may be invalidated by the given response to
* a particular request.
* @param host
* @param request
* @param response
*/
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request,
HttpResponse response);
/**
* Retrieve matching {@link HttpCacheEntry} from the cache if it exists
* @param host

View File

@ -113,6 +113,9 @@ public abstract class AbstractProtocolTest {
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class), EasyMock.isA(HttpResponse.class));
EasyMock.expectLastCall().anyTimes();
}
protected void behaveAsNonSharedCache() {

View File

@ -36,6 +36,8 @@ import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
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.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
@ -44,6 +46,8 @@ import static org.apache.http.impl.cookie.DateUtils.formatDate;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import static org.easymock.classextension.EasyMock.*;
import org.junit.Before;
import org.junit.Test;
@ -291,6 +295,27 @@ public class TestCacheInvalidator {
verifyMocks();
}
@Test
public void doesNotFlushEntryForUnsuccessfulResponse()
throws Exception {
response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_REQUEST, "Bad Request");
response.setHeader("ETag","\"new-etag\"");
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
@Test
public void flushesEntryIfFresherAndSpecifiedByNonCanonicalContentLocation()
throws Exception {

View File

@ -40,13 +40,16 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.impl.cookie.DateUtils;
import static org.apache.http.impl.cookie.DateUtils.formatDate;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HttpContext;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
/*
@ -56,6 +59,17 @@ import org.junit.Test;
*/
public class TestProtocolRecommendations extends AbstractProtocolTest {
private Date now;
private Date tenSecondsAgo;
@Override
@Before
public void setUp() {
super.setUp();
now = new Date();
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
}
/* "identity: The default (identity) encoding; the use of no
* transformation whatsoever. This content-coding is used only in the
* Accept-Encoding header, and SHOULD NOT be used in the
@ -92,7 +106,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp1 = HttpTestUtils.make200Response();
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","public,max-age=5");
resp1.setHeader("Etag","\"etag\"");
@ -231,7 +245,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
resp1.setHeader("Cache-Control", "public, max-age=5");
resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
backendExpectsAnyRequest().andReturn(resp1);
@ -274,7 +288,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
throws Exception {
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
originResponse.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
originResponse.setHeader("Date", formatDate(tenSecondsAgo));
originResponse.setHeader("Cache-Control","public, max-age=5");
originResponse.setHeader("ETag","\"etag\"");
@ -292,7 +306,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
throws Exception {
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
originResponse.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
originResponse.setHeader("Date", formatDate(tenSecondsAgo));
originResponse.setHeader("Cache-Control","public, max-age=5");
originResponse.setHeader("ETag","\"etag\"");
originResponse.addHeader("Age","10");
@ -440,14 +454,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
@Test
public void testDoesNotModifyDateOnRequests()
throws Exception {
request.setHeader("Date", DateUtils.formatDate(new Date()));
request.setHeader("Date", formatDate(new Date()));
testDoesNotModifyHeaderOnRequests("Date");
}
@Test
public void testDoesNotModifyDateOnResponses()
throws Exception {
originResponse.setHeader("Date", DateUtils.formatDate(new Date()));
originResponse.setHeader("Date", formatDate(new Date()));
testDoesNotModifyHeaderOnResponses("Date");
}
@ -461,7 +475,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
@Test
public void testDoesNotModifyExpiresOnResponses()
throws Exception {
originResponse.setHeader("Expires", DateUtils.formatDate(new Date()));
originResponse.setHeader("Expires", formatDate(new Date()));
testDoesNotModifyHeaderOnResponses("Expires");
}
@ -483,7 +497,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
@Test
public void testDoesNotModifyIfModifiedSinceOnRequests()
throws Exception {
request.setHeader("If-Modified-Since", DateUtils.formatDate(new Date()));
request.setHeader("If-Modified-Since", formatDate(new Date()));
testDoesNotModifyHeaderOnRequests("If-Modified-Since");
}
@ -506,14 +520,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
public void testDoesNotModifyIfUnmodifiedSinceOnRequests()
throws Exception {
request = new BasicHttpRequest("DELETE", "/", HttpVersion.HTTP_1_1);
request.setHeader("If-Unmodified-Since", DateUtils.formatDate(new Date()));
request.setHeader("If-Unmodified-Since", formatDate(new Date()));
testDoesNotModifyHeaderOnRequests("If-Unmodified-Since");
}
@Test
public void testDoesNotModifyLastModifiedOnResponses()
throws Exception {
originResponse.setHeader("Last-Modified", DateUtils.formatDate(new Date()));
originResponse.setHeader("Last-Modified", formatDate(new Date()));
testDoesNotModifyHeaderOnResponses("Last-Modified");
}
@ -599,12 +613,12 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Date twentySecondsAgo = new Date(now.getTime() - 20 * 1000L);
final String lmDate = DateUtils.formatDate(twentySecondsAgo);
final String lmDate = formatDate(twentySecondsAgo);
HttpRequest req1 =
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Last-Modified", lmDate);
resp1.setHeader("Cache-Control","max-age=5");
@ -644,13 +658,13 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Date twentySecondsAgo = new Date(now.getTime() - 20 * 1000L);
final String lmDate = DateUtils.formatDate(twentySecondsAgo);
final String lmDate = formatDate(twentySecondsAgo);
final String etag = "\"etag\"";
HttpRequest req1 =
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Last-Modified", lmDate);
resp1.setHeader("Cache-Control","max-age=5");
resp1.setHeader("ETag", etag);
@ -699,8 +713,8 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(now));
resp1.setHeader("Expires",DateUtils.formatDate(oneSecondAgo));
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires",formatDate(oneSecondAgo));
backendExpectsAnyRequest().andReturn(resp1);
@ -712,8 +726,8 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp2.setHeader("Date", DateUtils.formatDate(twoSecondsFromNow));
resp2.setHeader("Expires", DateUtils.formatDate(oneSecondFromNow));
resp2.setHeader("Date", formatDate(twoSecondsFromNow));
resp2.setHeader("Expires", formatDate(oneSecondFromNow));
resp2.setHeader("ETag","\"etag\"");
expect(mockBackend.execute(isA(HttpHost.class),
@ -752,7 +766,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("ETag","\"etag\"");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5");
backendExpectsAnyRequest().andReturn(resp1);
@ -761,14 +775,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(elevenSecondsAgo));
resp2.setHeader("Date", formatDate(elevenSecondsAgo));
backendExpectsAnyRequest().andReturn(resp2);
Capture<HttpRequest> cap = new Capture<HttpRequest>();
HttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("ETag","\"etag2\"");
resp3.setHeader("Date", DateUtils.formatDate(now));
resp3.setHeader("Date", formatDate(now));
resp3.setHeader("Cache-Control","max-age=5");
expect(mockBackend.execute(isA(HttpHost.class), capture(cap),
@ -884,7 +898,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req1.setHeader("User-Agent", "agent1");
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\"");
@ -896,7 +910,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req2.setHeader("User-Agent", "agent2");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Date", formatDate(tenSecondsAgo));
resp2.setHeader("Vary", "User-Agent");
resp2.setHeader("Cache-Control", "max-age=3600");
resp2.setHeader("ETag", "\"etag2\"");
@ -907,7 +921,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req3.setHeader("User-Agent", "agent3");
HttpResponse resp3 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp3.setHeader("Date", DateUtils.formatDate(now));
resp3.setHeader("Date", formatDate(now));
resp3.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequest().andReturn(resp3);
@ -924,8 +938,8 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertEquals(HttpStatus.SC_OK, result1.getStatusLine().getStatusCode());
assertEquals("\"etag1\"", result1.getFirstHeader("ETag").getValue());
assertEquals(DateUtils.formatDate(now), result1.getFirstHeader("Date").getValue());
assertEquals(DateUtils.formatDate(now), result2.getFirstHeader("Date").getValue());
assertEquals(formatDate(now), result1.getFirstHeader("Date").getValue());
assertEquals(formatDate(now), result2.getFirstHeader("Date").getValue());
}
@Test
@ -939,7 +953,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req1.setHeader("User-Agent", "agent1");
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\"");
@ -950,7 +964,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
req2.setHeader("User-Agent", "agent2");
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Date", formatDate(now));
resp2.setHeader("ETag", "\"etag1\"");
backendExpectsAnyRequest().andReturn(resp2);
@ -995,7 +1009,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("Vary","User-Agent");
resp2.setHeader("ETag", "\"etag2\"");
resp2.setHeader("Cache-Control","max-age=3600");
resp2.setHeader("Date", DateUtils.formatDate(new Date()));
resp2.setHeader("Date", formatDate(new Date()));
backendExpectsAnyRequest().andReturn(resp2);
@ -1024,4 +1038,44 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
}
}
}
/* "If a cache receives a successful response whose Content-Location
* field matches that of an existing cache entry for the same Request-
* URI, whose entity-tag differs from that of the existing entry, and
* whose Date is more recent than that of the existing entry, the
* existing entry SHOULD NOT be returned in response to future requests
* and SHOULD be deleted from the cache.
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6
*/
@Test
public void cachedEntryShouldNotBeUsedIfMoreRecentMentionInContentLocation()
throws Exception {
HttpRequest req1 = new HttpGet("http://foo.example.com/");
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader("ETag", "\"old-etag\"");
resp1.setHeader("Date", formatDate(tenSecondsAgo));
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = new HttpGet("http://foo.example.com/bar");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"new-etag\"");
resp2.setHeader("Date", formatDate(now));
resp2.setHeader("Content-Location", "http://foo.example.com/");
backendExpectsAnyRequest().andReturn(resp2);
HttpRequest req3 = new HttpGet("http://foo.example.com");
HttpResponse resp3 = HttpTestUtils.make200Response();
backendExpectsAnyRequest().andReturn(resp3);
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
impl.execute(host, req3);
verifyMocks();
}
}

View File

@ -2584,6 +2584,8 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
Capture<HttpRequest> cap = new Capture<HttpRequest>();
mockCache.flushInvalidatedCacheEntriesFor(host, request);
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class),
EasyMock.isA(HttpRequest.class), EasyMock.isA(HttpResponse.class));
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
EasyMock.expect(
mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.capture(cap),