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.HeaderElement;
import org.apache.http.HttpRequest;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCacheEntry;
@ -118,6 +119,35 @@ class CacheValidityPolicy {
public boolean proxyRevalidate(final HttpCacheEntry entry) {
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) {
Header dateHdr = entry.getFirstHeader(HTTP.DATE_HEADER);

View File

@ -513,7 +513,7 @@ public class CachingHttpClient implements HttpClient {
}
return false;
}
private String generateViaHeader(HttpMessage msg) {
final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
@ -691,11 +691,25 @@ public class CachingHttpClient implements HttpClient {
}
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,
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(
HttpHost target,
HttpRequest request,

View File

@ -31,6 +31,7 @@ import java.util.Date;
import java.util.Map;
import java.util.Random;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage;
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.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.junit.Assert;
public class HttpTestUtils {
@ -54,7 +57,7 @@ public class HttpTestUtils {
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
*/
private static final String[] HOP_BY_HOP_HEADERS = { "Connection", "Keep-Alive", "Proxy-Authenticate",
"Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
"Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
/*
* "Multiple message-header fields with the same field-name MAY be present
@ -64,15 +67,15 @@ public class HttpTestUtils {
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
*/
private static final String[] MULTI_HEADERS = { "Accept", "Accept-Charset", "Accept-Encoding",
"Accept-Language", "Allow", "Cache-Control", "Connection", "Content-Encoding",
"Content-Language", "Expect", "Pragma", "Proxy-Authenticate", "TE", "Trailer",
"Transfer-Encoding", "Upgrade", "Via", "Warning", "WWW-Authenticate" };
"Accept-Language", "Allow", "Cache-Control", "Connection", "Content-Encoding",
"Content-Language", "Expect", "Pragma", "Proxy-Authenticate", "TE", "Trailer",
"Transfer-Encoding", "Upgrade", "Via", "Warning", "WWW-Authenticate" };
private static final String[] SINGLE_HEADERS = { "Accept-Ranges", "Age", "Authorization",
"Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
"Date", "ETag", "Expires", "From", "Host", "If-Match", "If-Modified-Since",
"If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location",
"Max-Forwards", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server",
"User-Agent", "Vary" };
"Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
"Date", "ETag", "Expires", "From", "Host", "If-Match", "If-Modified-Since",
"If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location",
"Max-Forwards", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server",
"User-Agent", "Vary" };
/*
* Determines whether the given header name is considered a hop-by-hop
@ -150,7 +153,7 @@ public class HttpTestUtils {
public static boolean equivalent(RequestLine l1, RequestLine l2) {
return (l1.getMethod().equals(l2.getMethod())
&& l1.getProtocolVersion().equals(l2.getProtocolVersion()) && l1.getUri().equals(
l2.getUri()));
l2.getUri()));
}
/*
@ -202,13 +205,13 @@ public class HttpTestUtils {
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1.3
*/
public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2)
throws Exception {
throws Exception {
final boolean entitiesEquivalent = equivalent(r1.getEntity(), r2.getEntity());
if (!entitiesEquivalent) return false;
final boolean statusLinesEquivalent = semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine());
if (!statusLinesEquivalent) return false;
final boolean e2eHeadersEquivalentSubset = isEndToEndHeaderSubset(
r1, r2);
r1, r2);
return e2eHeadersEquivalentSubset;
}
@ -263,14 +266,14 @@ public class HttpTestUtils {
return makeCacheEntry(now, now, getStockHeaders(now),
getRandomBytes(128), variantMap);
}
public static HttpCacheEntry makeCacheEntry(Date requestDate,
Date responseDate, Header[] headers, byte[] bytes,
Map<String,String> variantMap) {
StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
return new HttpCacheEntry(requestDate, responseDate, statusLine, headers, new HeapResource(bytes), variantMap);
}
public static HttpCacheEntry makeCacheEntry(Header[] headers, byte[] bytes) {
Date now = new Date();
return makeCacheEntry(now, now, headers, bytes);
@ -297,4 +300,35 @@ public class HttpTestUtils {
out.setEntity(makeBody(128));
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;
import static org.junit.Assert.*;
import java.util.Date;
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.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.junit.Assert;
import org.apache.http.message.BasicHttpRequest;
import org.junit.Before;
import org.junit.Test;
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
public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
Header[] headers = {
new BasicHeader("Server", "MockServer/1.0")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(2147483648L, impl.getApparentAgeSecs(entry));
assertEquals(2147483648L, impl.getApparentAgeSecs(entry));
}
@Test
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
.formatDate(tenSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo, headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(4, impl.getApparentAgeSecs(entry));
assertEquals(4, impl.getApparentAgeSecs(entry));
}
@Test
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
.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now,tenSecondsAgo,headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(0, impl.getApparentAgeSecs(entry));
assertEquals(0, impl.getApparentAgeSecs(entry));
}
@Test
public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
protected long getApparentAgeSecs(HttpCacheEntry entry) {
return 6;
}
};
Assert.assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
}
@Test
public void testCorrectedReceivedAgeIsApparentAgeIfLarger() {
Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
protected long getApparentAgeSecs(HttpCacheEntry entry) {
return 10;
}
};
Assert.assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
assertEquals(10, impl.getCorrectedReceivedAgeSecs(entry));
}
@Test
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);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(4, impl.getResponseDelaySecs(entry));
assertEquals(4, impl.getResponseDelaySecs(entry));
}
@Test
public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
protected long getCorrectedReceivedAgeSecs(HttpCacheEntry entry) {
return 7;
@ -136,62 +130,50 @@ public class TestCacheValidityPolicy {
protected long getResponseDelaySecs(HttpCacheEntry entry) {
return 13;
}
};
Assert.assertEquals(20, impl.getCorrectedInitialAgeSecs(entry));
assertEquals(20, impl.getCorrectedInitialAgeSecs(entry));
}
@Test
public void testResidentTimeSecondsIsTimeSinceResponseTime() {
final Date now = new Date();
final Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
protected Date getCurrentDate() {
return now;
}
};
Assert.assertEquals(6, impl.getResidentTimeSecs(entry, now));
assertEquals(6, impl.getResidentTimeSecs(entry, now));
}
@Test
public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
protected long getCorrectedInitialAgeSecs(HttpCacheEntry entry) {
return 11;
}
@Override
protected long getResidentTimeSecs(HttpCacheEntry entry, Date d) {
return 17;
}
};
Assert.assertEquals(28, impl.getCurrentAgeSecs(entry, new Date()));
assertEquals(28, impl.getCurrentAgeSecs(entry, new Date()));
}
@Test
public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
}
@Test
public void testFreshnessLifetimeIsMaxAgeIfPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
}
@Test
@ -199,312 +181,269 @@ public class TestCacheValidityPolicy {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
new BasicHeader("Cache-Control", "s-maxage=20") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
new BasicHeader("Cache-Control", "s-maxage=10") };
entry = HttpTestUtils.makeCacheEntry(headers);
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
}
@Test
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"),
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
}
@Test
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"),
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
assertEquals(10, impl.getFreshnessLifetimeSecs(entry));
}
@Test
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[] {
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(4, impl.getFreshnessLifetimeSecs(entry));
assertEquals(4, impl.getFreshnessLifetimeSecs(entry));
}
@Test
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[] {
new BasicHeader("Date", DateUtils.formatDate(oneSecondAgo)),
new BasicHeader("Last-Modified", DateUtils.formatDate(elevenSecondsAgo))
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(1, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 0));
assertEquals(1, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 0));
}
@Test
public void testHeuristicFreshnessLifetimeDefaultsProperly() {
long defaultFreshness = 10;
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, defaultFreshness));
assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, defaultFreshness));
}
@Test
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[] {
new BasicHeader("Date", DateUtils.formatDate(elevenSecondsAgo)),
new BasicHeader("Last-Modified", DateUtils.formatDate(oneSecondAgo))
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertTrue(impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 10) >= 0);
assertTrue(impl.getHeuristicFreshnessLifetimeSecs(entry, 0.1f, 10) >= 0);
}
@Test
public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date();
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertSame(entry, e);
Assert.assertEquals(now, d);
assertSame(entry, e);
assertEquals(now, d);
return 6;
}
@Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e);
assertSame(entry, e);
return 10;
}
};
Assert.assertTrue(impl.isResponseFresh(entry, now));
assertTrue(impl.isResponseFresh(entry, now));
}
@Test
public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date();
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertEquals(now, d);
Assert.assertSame(entry, e);
assertEquals(now, d);
assertSame(entry, e);
return 6;
}
@Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e);
assertSame(entry, e);
return 6;
}
};
Assert.assertFalse(impl.isResponseFresh(entry, now));
assertFalse(impl.isResponseFresh(entry, now));
}
@Test
public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final Date now = new Date();
CacheValidityPolicy impl = new CacheValidityPolicy() {
impl = new CacheValidityPolicy() {
@Override
public long getCurrentAgeSecs(HttpCacheEntry e, Date d) {
Assert.assertEquals(now, d);
Assert.assertSame(entry, e);
assertEquals(now, d);
assertSame(entry, e);
return 10;
}
@Override
public long getFreshnessLifetimeSecs(HttpCacheEntry e) {
Assert.assertSame(entry, e);
assertSame(entry, e);
return 6;
}
};
Assert.assertFalse(impl.isResponseFresh(entry, now));
assertFalse(impl.isResponseFresh(entry, now));
}
@Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("ETag", "somevalue")};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertTrue(impl.isRevalidatable(entry));
assertTrue(impl.isRevalidatable(entry));
}
@Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertTrue(impl.isRevalidatable(entry));
assertTrue(impl.isRevalidatable(entry));
}
@Test
public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
Header[] headers = {
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
new BasicHeader("Cache-Control", "public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertFalse(impl.isRevalidatable(entry));
assertFalse(impl.isRevalidatable(entry));
}
@Test
public void testMalformedDateHeaderIsIgnored() {
Header[] headers = new Header[] { new BasicHeader("Date", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Date d = impl.getDateValue(entry);
Assert.assertNull(d);
assertNull(impl.getDateValue(entry));
}
@Test
public void testMalformedContentLengthReturnsNegativeOne() {
Header[] headers = new Header[] { new BasicHeader("Content-Length", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getContentLengthValue(entry);
Assert.assertEquals(-1, length);
assertEquals(-1, impl.getContentLengthValue(entry));
}
@Test
public void testNegativeAgeHeaderValueReturnsMaxAge() {
Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getAgeValue(entry);
Assert.assertEquals(CacheValidityPolicy.MAX_AGE, length);
assertEquals(CacheValidityPolicy.MAX_AGE, impl.getAgeValue(entry));
}
@Test
public void testMalformedAgeHeaderValueReturnsMaxAge() {
Header[] headers = new Header[] { new BasicHeader("Age", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
long length = impl.getAgeValue(entry);
Assert.assertEquals(CacheValidityPolicy.MAX_AGE, length);
assertEquals(CacheValidityPolicy.MAX_AGE, impl.getAgeValue(entry));
}
@Test
public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
long maxage = impl.getMaxAge(entry);
Assert.assertEquals(0, maxage);
assertEquals(0, impl.getMaxAge(entry));
}
@Test
public void testMalformedExpirationDateReturnsNull() {
Header[] headers = new Header[] { new BasicHeader("Expires", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Date expirationDate = impl.getExpirationDate(entry);
Assert.assertNull(expirationDate);
assertNull(impl.getExpirationDate(entry));
}
@Test
public void testMustRevalidateIsFalseIfDirectiveNotPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertFalse(impl.mustRevalidate(entry));
assertFalse(impl.mustRevalidate(entry));
}
@Test
public void testMustRevalidateIsTrueWhenDirectiveIsPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertTrue(impl.mustRevalidate(entry));
assertTrue(impl.mustRevalidate(entry));
}
@Test
public void testProxyRevalidateIsFalseIfDirectiveNotPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertFalse(impl.proxyRevalidate(entry));
assertFalse(impl.proxyRevalidate(entry));
}
@Test
public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
CacheValidityPolicy impl = new CacheValidityPolicy();
Assert.assertTrue(impl.proxyRevalidate(entry));
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

@ -307,7 +307,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertEquals(warning, result.getFirstHeader("Warning").getValue());
}
/*
* "A transparent proxy SHOULD NOT modify an end-to-end header unless
* the definition of that header requires or specifically allows that."

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());
}
}