HTTPCLIENT-1223: Forward port from 4.2.X branch to trunk.

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1376181 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2012-08-22 18:31:52 +00:00
parent 16a63149e1
commit b7d1d69ef6
6 changed files with 57 additions and 21 deletions

View File

@ -1,10 +1,12 @@
Changes since 4.2.1
-------------------
* [HTTPCLIENT-1223] Cache could be more aggressive on cache invalidations
from Content-Location. Contributed by Jon Moore <jonm at apache.org>.
* [HTTPCLIENT-1216] Added method to force clean thread-local used by DateUtils.
Contributed by Oleg Kalnichevski <olegk at apache.org>
Release 4.2.1
-------------------

View File

@ -199,7 +199,10 @@ class CacheInvalidator {
HttpCacheEntry entry = getEntry(cacheKey);
if (entry == null) return;
if (!responseDateNewerThanEntryDate(response, entry)) return;
// do not invalidate if response is strictly older than entry
// or if the etags match
if (responseDateOlderThanEntryDate(response, entry)) return;
if (!responseAndEntryEtagsDiffer(response, entry)) return;
flushUriIfSameHost(reqURL, canonURL);
@ -222,18 +225,20 @@ class CacheInvalidator {
return (!entryEtag.getValue().equals(responseEtag.getValue()));
}
private boolean responseDateNewerThanEntryDate(HttpResponse response,
private boolean responseDateOlderThanEntryDate(HttpResponse response,
HttpCacheEntry entry) {
Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
if (entryDateHeader == null || responseDateHeader == null) {
/* be conservative; should probably flush */
return false;
}
try {
Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
return responseDate.after(entryDate);
return responseDate.before(entryDate);
} catch (DateParseException e) {
/* flushing is always safe */
return false;
}
}

View File

@ -426,11 +426,13 @@ public class CachingHttpClient implements HttpClient {
flushEntriesInvalidatedByRequest(target, request);
if (!cacheableRequestPolicy.isServableFromCache(request)) {
log.debug("Request is not servable from cache");
return callBackend(target, request, context);
}
HttpCacheEntry entry = satisfyFromCache(target, request);
if (entry == null) {
log.debug("Cache miss");
return handleCacheMiss(target, request, context);
}
@ -444,12 +446,16 @@ public class CachingHttpClient implements HttpClient {
HttpResponse out = null;
Date now = getCurrentDate();
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
log.debug("Cache hit");
out = generateCachedResponse(request, context, entry, now);
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
out = generateGatewayTimeout(context);
} else if (validityPolicy.isRevalidatable(entry)) {
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) {
@ -464,12 +470,12 @@ public class CachingHttpClient implements HttpClient {
private HttpResponse revalidateCacheEntry(HttpHost target,
HttpRequest request, HttpContext context, HttpCacheEntry entry,
Date now) throws ClientProtocolException {
log.trace("Revalidating the cache entry");
try {
if (asynchRevalidator != null
&& !staleResponseNotAllowed(request, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
log.trace("Serving stale with asynchronous revalidation");
final HttpResponse resp = generateCachedResponse(request, context, entry, now);
asynchRevalidator.revalidateCacheEntry(target, request, context, entry);
@ -615,6 +621,7 @@ public class CachingHttpClient implements HttpClient {
for (Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for (HeaderElement elt : h.getElements()) {
if ("only-if-cached".equals(elt.getName())) {
log.trace("Request marked only-if-cached");
return false;
}
}

View File

@ -401,10 +401,10 @@ public class TestCacheInvalidator {
}
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfNotNewer()
public void doesNotFlushEntrySpecifiedByContentLocationIfOlder()
throws Exception {
response.setHeader("ETag","\"new-etag\"");
response.setHeader("Date", formatDate(now));
response.setHeader("Date", formatDate(tenSecondsAgo));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
@ -475,7 +475,7 @@ public class TestCacheInvalidator {
}
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasNoDate()
public void flushesEntrySpecifiedByContentLocationIfResponseHasNoDate()
throws Exception {
response.setHeader("ETag", "\"new-etag\"");
response.removeHeaders("Date");
@ -488,6 +488,7 @@ public class TestCacheInvalidator {
});
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
mockStorage.removeEntry(theURI);
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
@ -495,7 +496,7 @@ public class TestCacheInvalidator {
}
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoDate()
public void flushesEntrySpecifiedByContentLocationIfEntryHasNoDate()
throws Exception {
response.setHeader("ETag","\"new-etag\"");
response.setHeader("Date", formatDate(now));
@ -507,6 +508,7 @@ public class TestCacheInvalidator {
});
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
mockStorage.removeEntry(theURI);
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
@ -514,7 +516,7 @@ public class TestCacheInvalidator {
}
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasMalformedDate()
public void flushesEntrySpecifiedByContentLocationIfResponseHasMalformedDate()
throws Exception {
response.setHeader("ETag","\"new-etag\"");
response.setHeader("Date", "blarg");
@ -527,6 +529,7 @@ public class TestCacheInvalidator {
});
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
mockStorage.removeEntry(theURI);
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
@ -534,7 +537,7 @@ public class TestCacheInvalidator {
}
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasMalformedDate()
public void flushesEntrySpecifiedByContentLocationIfEntryHasMalformedDate()
throws Exception {
response.setHeader("ETag","\"new-etag\"");
response.setHeader("Date", formatDate(now));
@ -547,6 +550,7 @@ public class TestCacheInvalidator {
});
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
mockStorage.removeEntry(theURI);
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);

View File

@ -1752,5 +1752,27 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
}
@Test
public void issues304EvenWithWeakETag() throws Exception {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "max-age=300");
resp1.setHeader("ETag","W/\"weak-sauce\"");
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("If-None-Match","W/\"weak-sauce\"");
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
}
}

View File

@ -123,17 +123,13 @@
<section id="rfc2616compliance">
<title>RFC-2616 Compliance</title>
<para>HttpClient Cache makes an effort to be at least <emphasis>conditionally
<para>We believe HttpClient Cache is <emphasis>unconditionally
compliant</emphasis> with <ulink
url="http://www.ietf.org/rfc/rfc2616.txt">RFC-2616</ulink>. That is,
wherever the specification indicates MUST or MUST NOT for HTTP caches, the
caching layer attempts to behave in a way that satisfies those
requirements. This means the caching module won't produce incorrect
behavior when you drop it in. At the same time, the project is continuing
to work on unconditional compliance, which would add compliance with all the
SHOULDs and SHOULD NOTs, many of which we already comply with. We just can't
claim fully unconditional compliance until we satisfy <emphasis>all</emphasis>
of them.</para>
wherever the specification indicates MUST, MUST NOT, SHOULD, or SHOULD NOT
for HTTP caches, the caching layer attempts to behave in a way that satisfies
those requirements. This means the caching module won't produce incorrect
behavior when you drop it in. </para>
</section>
<section>
@ -226,7 +222,7 @@ case VALIDATED:
offers high performance, it may not be appropriate for your application due to
the limitation on size or because the cache entries are ephemeral and don't
survive an application restart. The current release includes support for storing
cache entries using Ehcache and memcached implementations, which allow for
cache entries using EhCache and memcached implementations, which allow for
spilling cache entries to disk or storing them in an external process.</para>
<para>If none of those options are suitable for your application, it is