HTTPCLIENT-1361: stale-while-revalidate should work also without last-modified nor etag

HTTPCLIENT-1368: stale-if-error request cache directive should also apply to non-revalidatable cache entries

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1499502 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Francois-Xavier Bonnet 2013-07-03 18:11:35 +00:00
parent efe3d7d7e2
commit 981aa247d7
5 changed files with 75 additions and 9 deletions

View File

@ -279,8 +279,7 @@ public class CachingExec implements ClientExecChain {
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
out = Proxies.enhanceResponse(generateGatewayTimeout(context));
} else if (validityPolicy.isRevalidatable(entry)
&& !(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
} else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
&& !suitabilityChecker.isConditional(request))) {
log.debug("Revalidating cache entry");
return revalidateCacheEntry(route, request, context, execAware, entry, now);

View File

@ -467,12 +467,9 @@ public class CachingHttpClient implements HttpClient {
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
out = generateGatewayTimeout(context);
} else if (validityPolicy.isRevalidatable(entry)) {
} else {
log.debug("Revalidating cache entry");
return revalidateCacheEntry(target, request, context, entry, now);
} else {
log.debug("Cache entry not usable; calling backend");
return callBackend(target, request, context);
}
if (context != null) {
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);

View File

@ -56,6 +56,7 @@ import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.easymock.IExpectationSetters;
import org.easymock.classextension.EasyMock;
import org.junit.Assert;
@ -187,7 +188,12 @@ public class TestCachingExec extends TestCachingExecChain {
getCacheEntryReturns(mockCacheEntry);
cacheEntrySuitable(false);
cacheEntryValidatable(false);
implExpectsAnyRequestAndReturn(mockBackendResponse);
expect(mockConditionalRequestBuilder.buildConditionalRequest(request, mockCacheEntry))
.andReturn(request);
backendExpectsRequestAndReturn(request, mockBackendResponse);
expect(mockBackendResponse.getProtocolVersion()).andReturn(HttpVersion.HTTP_1_1);
expect(mockBackendResponse.getStatusLine()).andReturn(
new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "Ok"));
replayMocks();
final HttpResponse result = impl.execute(route, request, context);
@ -196,7 +202,7 @@ public class TestCachingExec extends TestCachingExecChain {
Assert.assertSame(mockBackendResponse, result);
Assert.assertEquals(0, impl.getCacheMisses());
Assert.assertEquals(1, impl.getCacheHits());
Assert.assertEquals(0, impl.getCacheUpdates());
Assert.assertEquals(1, impl.getCacheUpdates());
}
@Test

View File

@ -1650,7 +1650,7 @@ public abstract class TestCachingExecChain {
protected void cacheEntryValidatable(final boolean b) {
expect(mockValidityPolicy.isRevalidatable(
(HttpCacheEntry)anyObject())).andReturn(b);
(HttpCacheEntry)anyObject())).andReturn(b).anyTimes();
}
protected void cacheEntryMustRevalidate(final boolean b) {

View File

@ -239,6 +239,31 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpTestUtils.assert110WarningFound(result);
}
@Test
public void testStaleIfErrorInRequestIsTrueReturnsStaleNonRevalidatableEntryWithWarning()
throws Exception {
final Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
final HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "public, max-age=5");
backendExpectsAnyRequestAndReturn(resp1);
final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
req2.setHeader("Cache-Control", "public, stale-if-error=60");
final HttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequestAndReturn(resp2);
replayMocks();
impl.execute(route, req1, context, null);
final HttpResponse result = impl.execute(route, req2, context, null);
verifyMocks();
HttpTestUtils.assert110WarningFound(result);
}
@Test
public void testStaleIfErrorInResponseIsFalseReturnsError()
throws Exception{
@ -341,6 +366,45 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
assertTrue(warning110Found);
}
@Test
public void testStaleWhileRevalidateReturnsStaleNonRevalidatableEntryWithWarning()
throws Exception {
config = CacheConfig.custom().setMaxCacheEntries(MAX_ENTRIES).setMaxObjectSize(MAX_BYTES)
.setAsynchronousWorkersMax(1).build();
impl = new CachingExec(mockBackend, cache, config, new AsynchronousValidator(config));
final HttpRequestWrapper req1 = HttpRequestWrapper.wrap(new BasicHttpRequest("GET", "/",
HttpVersion.HTTP_1_1));
final HttpResponse resp1 = HttpTestUtils.make200Response();
final Date now = new Date();
final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
backendExpectsAnyRequestAndReturn(resp1).times(1, 2);
final HttpRequestWrapper req2 = HttpRequestWrapper.wrap(new BasicHttpRequest("GET", "/",
HttpVersion.HTTP_1_1));
replayMocks();
impl.execute(route, req1, context, null);
final HttpResponse result = impl.execute(route, req2, context, null);
verifyMocks();
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
boolean warning110Found = false;
for (final Header h : result.getHeaders("Warning")) {
for (final WarningValue wv : WarningValue.getWarningValues(h)) {
if (wv.getWarnCode() == 110) {
warning110Found = true;
break;
}
}
}
assertTrue(warning110Found);
}
@Test
public void testCanAlsoServeStale304sWhileRevalidating()
throws Exception {