HTTPCLIENT-975: committed patch to support stale-if-error from

RFC5861, with thanks to Mohammed Azeem Uddin
(mohammedazeem_uddin at comcast dot com).


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1049179 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2010-12-14 17:18:06 +00:00
parent 8aef0609de
commit a91847b57c
6 changed files with 371 additions and 197 deletions

View File

@ -30,6 +30,7 @@ import java.util.Date;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HeaderElement; import org.apache.http.HeaderElement;
import org.apache.http.HttpRequest;
import org.apache.http.annotation.Immutable; import org.apache.http.annotation.Immutable;
import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCacheEntry; import org.apache.http.client.cache.HttpCacheEntry;
@ -119,6 +120,35 @@ class CacheValidityPolicy {
return hasCacheControlDirective(entry, "proxy-revalidate"); return hasCacheControlDirective(entry, "proxy-revalidate");
} }
public boolean mayReturnStaleIfError(HttpRequest request,
HttpCacheEntry entry, Date now) {
long stalenessSecs = getStalenessSecs(entry, now);
return mayReturnStaleIfError(request.getHeaders("Cache-Control"),
stalenessSecs)
|| mayReturnStaleIfError(entry.getHeaders("Cache-Control"),
stalenessSecs);
}
private boolean mayReturnStaleIfError(Header[] headers, long stalenessSecs) {
boolean result = false;
for(Header h : headers) {
for(HeaderElement elt : h.getElements()) {
if ("stale-if-error".equals(elt.getName())) {
try {
int staleIfErrorSecs = Integer.parseInt(elt.getValue());
if (stalenessSecs <= staleIfErrorSecs) {
result = true;
break;
}
} catch (NumberFormatException nfe) {
// skip malformed directive
}
}
}
}
return result;
}
protected Date getDateValue(final HttpCacheEntry entry) { protected Date getDateValue(final HttpCacheEntry entry) {
Header dateHdr = entry.getFirstHeader(HTTP.DATE_HEADER); Header dateHdr = entry.getFirstHeader(HTTP.DATE_HEADER);
if (dateHdr == null) if (dateHdr == null)

View File

@ -692,10 +692,24 @@ public class CachingHttpClient implements HttpClient {
return responseGenerator.generateResponse(updatedEntry); return responseGenerator.generateResponse(updatedEntry);
} }
if (staleIfErrorAppliesTo(statusCode)
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
final HttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
return cachedResponse;
}
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate, return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
backendResponse); backendResponse);
} }
private boolean staleIfErrorAppliesTo(int statusCode) {
return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
|| statusCode == HttpStatus.SC_BAD_GATEWAY
|| statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
|| statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
}
HttpResponse handleBackendResponse( HttpResponse handleBackendResponse(
HttpHost target, HttpHost target,
HttpRequest request, HttpRequest request,

View File

@ -31,6 +31,7 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage; import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
@ -43,8 +44,10 @@ import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.cookie.DateUtils; import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
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;
import org.junit.Assert;
public class HttpTestUtils { public class HttpTestUtils {
@ -297,4 +300,35 @@ public class HttpTestUtils {
out.setEntity(makeBody(128)); out.setEntity(makeBody(128));
return out; return out;
} }
public static final HttpResponse make200Response(Date date, String cacheControl) {
HttpResponse response = HttpTestUtils.make200Response();
response.setHeader("Date", DateUtils.formatDate(date));
response.setHeader("Cache-Control",cacheControl);
response.setHeader("Etag","\"etag\"");
return response;
}
public static final void assert110WarningFound(HttpResponse response) {
boolean found110Warning = false;
for(Header h : response.getHeaders("Warning")) {
for(HeaderElement elt : h.getElements()) {
String[] parts = elt.getName().split("\\s");
if ("110".equals(parts[0])) {
found110Warning = true;
break;
}
}
}
Assert.assertTrue(found110Warning);
}
public static HttpRequest makeDefaultRequest() {
return new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
}
public static HttpResponse make500Response() {
return new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
}
} }

View File

@ -26,107 +26,101 @@
*/ */
package org.apache.http.impl.client.cache; package org.apache.http.impl.client.cache;
import static org.junit.Assert.*;
import java.util.Date; import java.util.Date;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.client.cache.HttpCacheEntry; import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.impl.cookie.DateUtils; import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import org.junit.Assert; import org.apache.http.message.BasicHttpRequest;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class TestCacheValidityPolicy { public class TestCacheValidityPolicy {
private CacheValidityPolicy impl;
private Date now;
private Date oneSecondAgo;
private Date sixSecondsAgo;
private Date tenSecondsAgo;
private Date elevenSecondsAgo;
@Before
public void setUp() {
impl = new CacheValidityPolicy();
now = new Date();
oneSecondAgo = new Date(now.getTime() - 1 * 1000L);
sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
elevenSecondsAgo = new Date(now.getTime() - 11 * 1000L);
}
@Test @Test
public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() { public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
Header[] headers = { Header[] headers = {
new BasicHeader("Server", "MockServer/1.0") new BasicHeader("Server", "MockServer/1.0")
}; };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(2147483648L, impl.getApparentAgeSecs(entry));
Assert.assertEquals(2147483648L, impl.getApparentAgeSecs(entry));
} }
@Test @Test
public void testApparentAgeIsResponseReceivedTimeLessDateHeader() { public void testApparentAgeIsResponseReceivedTimeLessDateHeader() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Header[] headers = new Header[] { new BasicHeader("Date", DateUtils Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
.formatDate(tenSecondsAgo)) }; .formatDate(tenSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(4, impl.getApparentAgeSecs(entry));
Assert.assertEquals(4, impl.getApparentAgeSecs(entry));
} }
@Test @Test
public void testNegativeApparentAgeIsBroughtUpToZero() { public void testNegativeApparentAgeIsBroughtUpToZero() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Header[] headers = new Header[] { new BasicHeader("Date", DateUtils Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
.formatDate(sixSecondsAgo)) }; .formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now,tenSecondsAgo,headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now,tenSecondsAgo,headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(0, impl.getApparentAgeSecs(entry));
Assert.assertEquals(0, impl.getApparentAgeSecs(entry));
} }
@Test @Test
public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() { public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
Header[] headers = new Header[] { new BasicHeader("Age", "10"), }; Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
protected long getApparentAgeSecs(HttpCacheEntry entry) { protected long getApparentAgeSecs(HttpCacheEntry entry) {
return 6; return 6;
} }
}; };
assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
Assert.assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
} }
@Test @Test
public void testCorrectedReceivedAgeIsApparentAgeIfLarger() { public void testCorrectedReceivedAgeIsApparentAgeIfLarger() {
Header[] headers = new Header[] { new BasicHeader("Age", "6"), }; Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
protected long getApparentAgeSecs(HttpCacheEntry entry) { protected long getApparentAgeSecs(HttpCacheEntry entry) {
return 10; return 10;
} }
}; };
assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
Assert.assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
} }
@Test @Test
public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() { public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(4, impl.getResponseDelaySecs(entry));
Assert.assertEquals(4, impl.getResponseDelaySecs(entry));
} }
@Test @Test
public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() { public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
CacheValidityPolicy impl = new CacheValidityPolicy() { impl = new CacheValidityPolicy() {
@Override @Override
protected long getCorrectedReceivedAgeSecs(HttpCacheEntry entry) { protected long getCorrectedReceivedAgeSecs(HttpCacheEntry entry) {
return 7; return 7;
@ -136,62 +130,50 @@ public class TestCacheValidityPolicy {
protected long getResponseDelaySecs(HttpCacheEntry entry) { protected long getResponseDelaySecs(HttpCacheEntry entry) {
return 13; return 13;
} }
}; };
Assert.assertEquals(20, impl.getCorrectedInitialAgeSecs(entry)); assertEquals(20, impl.getCorrectedInitialAgeSecs(entry));
} }
@Test @Test
public void testResidentTimeSecondsIsTimeSinceResponseTime() { public void testResidentTimeSecondsIsTimeSinceResponseTime() {
final Date now = new Date();
final Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
protected Date getCurrentDate() { protected Date getCurrentDate() {
return now; return now;
} }
}; };
assertEquals(6, impl.getResidentTimeSecs(entry, now));
Assert.assertEquals(6, impl.getResidentTimeSecs(entry, now));
} }
@Test @Test
public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() { public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
CacheValidityPolicy impl = new CacheValidityPolicy() { impl = new CacheValidityPolicy() {
@Override @Override
protected long getCorrectedInitialAgeSecs(HttpCacheEntry entry) { protected long getCorrectedInitialAgeSecs(HttpCacheEntry entry) {
return 11; return 11;
} }
@Override @Override
protected long getResidentTimeSecs(HttpCacheEntry entry, Date d) { protected long getResidentTimeSecs(HttpCacheEntry entry, Date d) {
return 17; return 17;
} }
}; };
Assert.assertEquals(28, impl.getCurrentAgeSecs(entry, new Date())); assertEquals(28, impl.getCurrentAgeSecs(entry, new Date()));
} }
@Test @Test
public void testFreshnessLifetimeIsSMaxAgeIfPresent() { public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
public void testFreshnessLifetimeIsMaxAgeIfPresent() { public void testFreshnessLifetimeIsMaxAgeIfPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
@ -199,312 +181,269 @@ public class TestCacheValidityPolicy {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"), Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
new BasicHeader("Cache-Control", "s-maxage=20") }; new BasicHeader("Cache-Control", "s-maxage=20") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"), headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
new BasicHeader("Cache-Control", "s-maxage=10") }; new BasicHeader("Cache-Control", "s-maxage=10") };
entry = HttpTestUtils.makeCacheEntry(headers); entry = HttpTestUtils.makeCacheEntry(headers);
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry)); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() { public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"), Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() { public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"), Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"),
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() { public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
Date now = new Date();
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
Header[] headers = new Header[] { Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) }; new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(4, impl.getFreshnessLifetimeSecs(entry));
Assert.assertEquals(4, impl.getFreshnessLifetimeSecs(entry));
} }
@Test @Test
public void testHeuristicFreshnessLifetime() { public void testHeuristicFreshnessLifetime() {
Date now = new Date();
Date oneSecondAgo = new Date(now.getTime() - 1 * 1000L);
Date elevenSecondsAgo = new Date(now.getTime() - 11 * 1000L);
Header[] headers = new Header[] { Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)), new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
new BasicHeader("Last-Modified", DateUtils.formatDate(elevenSecondsAgo)) new BasicHeader("Last-Modified", DateUtils.formatDate(elevenSecondsAgo))
}; };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertEquals(1, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 0));
Assert.assertEquals(1, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 0));
} }
@Test @Test
public void testHeuristicFreshnessLifetimeDefaultsProperly() { public void testHeuristicFreshnessLifetimeDefaultsProperly() {
long defaultFreshness = 10; long defaultFreshness = 10;
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, defaultFreshness));
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, defaultFreshness));
} }
@Test @Test
public void testHeuristicFreshnessLifetimeIsNonNegative() { public void testHeuristicFreshnessLifetimeIsNonNegative() {
Date now = new Date();
Date oneSecondAgo = new Date(now.getTime() - 1 * 1000L);
Date elevenSecondsAgo = new Date(now.getTime() - 1 * 1000L);
Header[] headers = new Header[] { Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(elevenSecondsAgo)), new BasicHeader("Date", DateUtils.formatDate(elevenSecondsAgo)),
new BasicHeader("Last-Modified", DateUtils.formatDate(oneSecondAgo)) new BasicHeader("Last-Modified", DateUtils.formatDate(oneSecondAgo))
}; };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertTrue(impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 10) >= 0);
Assert.assertTrue(impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 10) >= 0);
} }
@Test @Test
public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() { public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date(); impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) { public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertSame(entry, e); assertSame(entry, e);
Assert.assertEquals(now, d); assertEquals(now, d);
return 6; return 6;
} }
@Override @Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) { public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e); assertSame(entry, e);
return 10; return 10;
} }
}; };
assertTrue(impl.isResponseFresh(entry, now));
Assert.assertTrue(impl.isResponseFresh(entry, now));
} }
@Test @Test
public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() { public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date(); impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) { public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertEquals(now, d); assertEquals(now, d);
Assert.assertSame(entry, e); assertSame(entry, e);
return 6; return 6;
} }
@Override @Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) { public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e); assertSame(entry, e);
return 6; return 6;
} }
}; };
assertFalse(impl.isResponseFresh(entry, now));
Assert.assertFalse(impl.isResponseFresh(entry, now));
} }
@Test @Test
public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() { public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date(); impl = new CacheValidityPolicy() {
CacheValidityPolicy impl = new CacheValidityPolicy() {
@Override @Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) { public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertEquals(now, d); assertEquals(now, d);
Assert.assertSame(entry, e); assertSame(entry, e);
return 10; return 10;
} }
@Override @Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) { public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e); assertSame(entry, e);
return 6; return 6;
} }
}; };
assertFalse(impl.isResponseFresh(entry, now));
Assert.assertFalse(impl.isResponseFresh(entry, now));
} }
@Test @Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() { public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
Header[] headers = { Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())), new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("ETag", "somevalue")}; new BasicHeader("ETag", "somevalue")};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertTrue(impl.isRevalidatable(entry));
Assert.assertTrue(impl.isRevalidatable(entry));
} }
@Test @Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() { public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
Header[] headers = { Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())), new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) }; new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertTrue(impl.isRevalidatable(entry));
Assert.assertTrue(impl.isRevalidatable(entry));
} }
@Test @Test
public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() { public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
Header[] headers = { Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())), new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("Cache-Control", "public") }; new BasicHeader("Cache-Control", "public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertFalse(impl.isRevalidatable(entry));
Assert.assertFalse(impl.isRevalidatable(entry));
} }
@Test @Test
public void testMalformedDateHeaderIsIgnored() { public void testMalformedDateHeaderIsIgnored() {
Header[] headers = new Header[] { new BasicHeader("Date", "asdf") }; Header[] headers = new Header[] { new BasicHeader("Date", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertNull(impl.getDateValue(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
Date d = impl.getDateValue(entry);
Assert.assertNull(d);
} }
@Test @Test
public void testMalformedContentLengthReturnsNegativeOne() { public void testMalformedContentLengthReturnsNegativeOne() {
Header[] headers = new Header[] { new BasicHeader("Content-Length", "asdf") }; Header[] headers = new Header[] { new BasicHeader("Content-Length", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(-1, impl.getContentLengthValue(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getContentLengthValue(entry);
Assert.assertEquals(-1, length);
} }
@Test @Test
public void testNegativeAgeHeaderValueReturnsMaxAge() { public void testNegativeAgeHeaderValueReturnsMaxAge() {
Header[] headers = new Header[] { new BasicHeader("Age", "-100") }; Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(CacheValidityPolicy.MAX_AGE, impl.getAgeValue(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getAgeValue(entry);
Assert.assertEquals(CacheValidityPolicy.MAX_AGE, length);
} }
@Test @Test
public void testMalformedAgeHeaderValueReturnsMaxAge() { public void testMalformedAgeHeaderValueReturnsMaxAge() {
Header[] headers = new Header[] { new BasicHeader("Age", "asdf") }; Header[] headers = new Header[] { new BasicHeader("Age", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(CacheValidityPolicy.MAX_AGE, impl.getAgeValue(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getAgeValue(entry);
Assert.assertEquals(CacheValidityPolicy.MAX_AGE, length);
} }
@Test @Test
public void testMalformedCacheControlMaxAgeHeaderReturnsZero() { public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(0, impl.getMaxAge(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
long maxage = impl.getMaxAge(entry);
Assert.assertEquals(0, maxage);
} }
@Test @Test
public void testMalformedExpirationDateReturnsNull() { public void testMalformedExpirationDateReturnsNull() {
Header[] headers = new Header[] { new BasicHeader("Expires", "asdf") }; Header[] headers = new Header[] { new BasicHeader("Expires", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertNull(impl.getExpirationDate(entry));
CacheValidityPolicy impl = new CacheValidityPolicy();
Date expirationDate = impl.getExpirationDate(entry);
Assert.assertNull(expirationDate);
} }
@Test @Test
public void testMustRevalidateIsFalseIfDirectiveNotPresent() { public void testMustRevalidateIsFalseIfDirectiveNotPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertFalse(impl.mustRevalidate(entry));
Assert.assertFalse(impl.mustRevalidate(entry));
} }
@Test @Test
public void testMustRevalidateIsTrueWhenDirectiveIsPresent() { public void testMustRevalidateIsTrueWhenDirectiveIsPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertTrue(impl.mustRevalidate(entry));
Assert.assertTrue(impl.mustRevalidate(entry));
} }
@Test @Test
public void testProxyRevalidateIsFalseIfDirectiveNotPresent() { public void testProxyRevalidateIsFalseIfDirectiveNotPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertFalse(impl.proxyRevalidate(entry));
Assert.assertFalse(impl.proxyRevalidate(entry));
} }
@Test @Test
public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() { public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") }; Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers); HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy(); assertTrue(impl.proxyRevalidate(entry));
Assert.assertTrue(impl.proxyRevalidate(entry));
} }
@Test
public void testMayReturnStaleIfErrorInResponseIsTrueWithinStaleness(){
Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-if-error=15")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
}
@Test
public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
req.setHeader("Cache-Control","stale-if-error=15");
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
}
@Test
public void testMayNotReturnStaleIfErrorInResponseAndAfterResponseWindow(){
Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-if-error=1")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
}
@Test
public void testMayNotReturnStaleIfErrorInResponseAndAfterRequestWindow(){
Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
req.setHeader("Cache-Control","stale-if-error=1");
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
}
} }

View File

@ -0,0 +1,157 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.impl.client.cache;
import static org.junit.Assert.assertEquals;
import java.util.Date;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.junit.Test;
/**
* A suite of acceptance tests for compliance with RFC5861, which
* describes the stale-if-error and stale-while-revalidate
* Cache-Control extensions.
*/
public class TestRFC5861Compliance extends AbstractProtocolTest {
/*
* "The stale-if-error Cache-Control extension indicates that when an
* error is encountered, a cached stale response MAY be used to satisfy
* the request, regardless of other freshness information.When used as a
* request Cache-Control extension, its scope of application is the request
* it appears in; when used as a response Cache-Control extension, its
* scope is any request applicable to the cached response in which it
* occurs.Its value indicates the upper limit to staleness; when the cached
* response is more stale than the indicated amount, the cached response
* SHOULD NOT be used to satisfy the request, absent other information.
* In this context, an error is any situation that would result in a
* 500, 502, 503, or 504 HTTP response status code being returned."
*
* http://tools.ietf.org/html/rfc5861
*/
@Test
public void testStaleIfErrorInResponseIsTrueReturnsStaleEntryWithWarning()
throws Exception{
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result);
}
@Test
public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
throws Exception{
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","public, max-age=5, stale-if-error=60");
HttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
HttpTestUtils.assert110WarningFound(result);
}
@Test
public void testStaleIfErrorInResponseIsFalseReturnsError()
throws Exception{
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=2");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
result.getStatusLine().getStatusCode());
}
@Test
public void testStaleIfErrorInRequestIsFalseReturnsError()
throws Exception{
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","stale-if-error=2");
HttpResponse resp2 = HttpTestUtils.make500Response();
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
result.getStatusLine().getStatusCode());
}
}