diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java index fab7bff63..2440c3d80 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.async.AsyncExecCallback; @@ -168,12 +169,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler static class AsyncExecCallbackWrapper implements AsyncExecCallback { - private final AsyncExecCallback asyncExecCallback; private final Runnable command; + private final Consumer exceptionConsumer; - AsyncExecCallbackWrapper(final AsyncExecCallback asyncExecCallback, final Runnable command) { - this.asyncExecCallback = asyncExecCallback; + AsyncExecCallbackWrapper(final Runnable command, final Consumer exceptionConsumer) { this.command = command; + this.exceptionConsumer = exceptionConsumer; } @Override @@ -194,7 +195,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler @Override public void failed(final Exception cause) { - asyncExecCallback.failed(cause); + if (exceptionConsumer != null) { + exceptionConsumer.accept(cause); + } } } @@ -603,7 +606,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) { LOG.debug("Cache hit"); try { - final SimpleHttpResponse cacheResponse = generateCachedResponse(hit.entry, request, context); + final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, hit.entry); triggerResponse(cacheResponse, scope, asyncExecCallback); } catch (final ResourceIOException ex) { recordCacheFailure(target, request); @@ -631,17 +634,47 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler LOG.debug("Request is not repeatable; calling backend"); callBackend(target, request, entityProducer, scope, chain, asyncExecCallback); } else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) { - LOG.debug("Cache entry with NOT_MODIFIED does not match the non-conditional request; calling backend"); + LOG.debug("Non-modified cache entry does not match the non-conditional request; calling backend"); callBackend(target, request, entityProducer, scope, chain, asyncExecCallback); - } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED || cacheSuitability == CacheSuitability.STALE) { - LOG.debug("Revalidating cache entry"); - final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry); - if (cacheRevalidator != null - && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now) - && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) { + } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) { + LOG.debug("Revalidation required; revalidating cache entry"); + revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() { + + private final AtomicBoolean committed = new AtomicBoolean(); + + @Override + public AsyncDataConsumer handleResponse(final HttpResponse response, + final EntityDetails entityDetails) throws HttpException, IOException { + committed.set(true); + return asyncExecCallback.handleResponse(response, entityDetails); + } + + @Override + public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException { + asyncExecCallback.handleInformationResponse(response); + } + + @Override + public void completed() { + asyncExecCallback.completed(); + } + + @Override + public void failed(final Exception cause) { + if (!committed.get() && cause instanceof IOException) { + final SimpleHttpResponse cacheResponse = generateGatewayTimeout(scope.clientContext); + LOG.debug(cause.getMessage(), cause); + triggerResponse(cacheResponse, scope, asyncExecCallback); + } else { + asyncExecCallback.failed(cause); + } + } + + }); + } else if (cacheSuitability == CacheSuitability.STALE_WHILE_REVALIDATED) { + if (cacheRevalidator != null) { LOG.debug("Serving stale with asynchronous revalidation"); try { - final SimpleHttpResponse cacheResponse = generateCachedResponse(hit.entry, request, context); final String exchangeId = ExecSupport.getNextExchangeId(); context.setExchangeId(exchangeId); final AsyncExecChain.Scope fork = new AsyncExecChain.Scope( @@ -656,30 +689,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler cacheRevalidator.revalidateCacheEntry( hit.getEntryKey(), asyncExecCallback, - asyncExecCallback1 -> revalidateCacheEntry(requestCacheControl, responseCacheControl, - hit, target, request, entityProducer, fork, chain, asyncExecCallback1)); + c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c)); + final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, context, hit.entry); triggerResponse(cacheResponse, scope, asyncExecCallback); - } catch (final ResourceIOException ex) { - if (staleIfErrorEnabled) { - if (LOG.isDebugEnabled()) { - LOG.debug("Serving stale response due to IOException and stale-if-error enabled"); - } - try { - final SimpleHttpResponse cacheResponse = generateCachedResponse(hit.entry, request, context); - triggerResponse(cacheResponse, scope, asyncExecCallback); - } catch (final ResourceIOException ex2) { - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to generate cached response, falling back to backend", ex2); - } - callBackend(target, request, entityProducer, scope, chain, asyncExecCallback); - } - } else { - asyncExecCallback.failed(ex); - } + } catch (final IOException ex) { + asyncExecCallback.failed(ex); } } else { - revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback); + LOG.debug("Revalidating stale cache entry (asynchronous revalidation disabled)"); + revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback); } + } else if (cacheSuitability == CacheSuitability.STALE) { + LOG.debug("Revalidating stale cache entry"); + revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback); } else { LOG.debug("Cache entry not usable; calling backend"); callBackend(target, request, entityProducer, scope, chain, asyncExecCallback); @@ -688,7 +710,6 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler } void revalidateCacheEntry( - final RequestCacheControl requestCacheControl, final ResponseCacheControl responseCacheControl, final CacheHit hit, final HttpHost target, @@ -720,17 +741,11 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler @Override public void completed(final CacheHit updated) { - if (suitabilityChecker.isConditional(request) - && suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) { - final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updated.entry); + try { + final SimpleHttpResponse cacheResponse = generateCachedResponse(request, scope.clientContext, updated.entry); triggerResponse(cacheResponse, scope, asyncExecCallback); - } else { - try { - final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry); - triggerResponse(cacheResponse, scope, asyncExecCallback); - } catch (final ResourceIOException ex) { - asyncExecCallback.failed(ex); - } + } catch (final ResourceIOException ex) { + asyncExecCallback.failed(ex); } } @@ -762,12 +777,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler recordCacheUpdate(scope.clientContext); } if (statusCode == HttpStatus.SC_NOT_MODIFIED) { - return new AsyncExecCallbackWrapper(asyncExecCallback, () -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate)); - } - if (staleIfErrorAppliesTo(statusCode) - && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate()) - && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) { - return new AsyncExecCallbackWrapper(asyncExecCallback, this::triggerResponseStaleCacheEntry); + return new AsyncExecCallbackWrapper(() -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate), asyncExecCallback::failed); } return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback); } @@ -780,12 +790,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler final Instant responseDate1 = getCurrentDate(); final AsyncExecCallback callback1; - if (revalidationResponseIsTooOld(backendResponse1, hit.entry)) { + if (HttpCacheEntry.isNewer(hit.entry, backendResponse1)) { final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest( BasicRequestBuilder.copy(scope.originalRequest).build()); - callback1 = new AsyncExecCallbackWrapper(asyncExecCallback, () -> chainProceed(unconditional, entityProducer, scope, chain, new AsyncExecCallback() { + callback1 = new AsyncExecCallbackWrapper(() -> chainProceed(unconditional, entityProducer, scope, chain, new AsyncExecCallback() { @Override public AsyncDataConsumer handleResponse( @@ -827,7 +837,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler } } - })); + }), asyncExecCallback::failed); } else { callback1 = evaluateResponse(backendResponse1, responseDate1); } @@ -869,6 +879,71 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler } + void revalidateCacheEntryWithFallback( + final RequestCacheControl requestCacheControl, + final ResponseCacheControl responseCacheControl, + final CacheHit hit, + final HttpHost target, + final HttpRequest request, + final AsyncEntityProducer entityProducer, + final AsyncExecChain.Scope scope, + final AsyncExecChain chain, + final AsyncExecCallback asyncExecCallback) { + revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() { + + private final AtomicBoolean committed = new AtomicBoolean(); + + @Override + public AsyncDataConsumer handleResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException { + final int status = response.getCode(); + if (staleIfErrorAppliesTo(status) && + suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) { + return null; + } else { + committed.set(true); + return asyncExecCallback.handleResponse(response, entityDetails); + } + } + + @Override + public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException { + asyncExecCallback.handleInformationResponse(response); + } + + @Override + public void completed() { + if (committed.get()) { + asyncExecCallback.completed(); + } else { + try { + final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, scope.clientContext, hit.entry); + triggerResponse(cacheResponse, scope, asyncExecCallback); + } catch (final IOException ex) { + asyncExecCallback.failed(ex); + } + } + } + + @Override + public void failed(final Exception cause) { + if (!committed.get() && + cause instanceof IOException && + suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) { + try { + final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, scope.clientContext, hit.entry); + triggerResponse(cacheResponse, scope, asyncExecCallback); + } catch (final IOException ex) { + asyncExecCallback.failed(cause); + } + } else { + LOG.debug(cause.getMessage(), cause); + final SimpleHttpResponse cacheResponse = generateGatewayTimeout(scope.clientContext); + triggerResponse(cacheResponse, scope, asyncExecCallback); + } + } + + }); + } private void handleCacheMiss( final RequestCacheControl requestCacheControl, final CacheHit partialMatch, @@ -954,7 +1029,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler @Override public void completed(final CacheHit hit) { - if (shouldSendNotModifiedResponse(request, hit.entry)) { + if (shouldSendNotModifiedResponse(request, hit.entry, Instant.now())) { final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(hit.entry); triggerResponse(cacheResponse, scope, asyncExecCallback); } else { @@ -1055,21 +1130,21 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG); if (resultEtagHeader == null) { LOG.warn("304 response did not contain ETag"); - callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback)); + callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed); } else { final String resultEtag = resultEtagHeader.getValue(); final CacheHit match = variantMap.get(resultEtag); if (match == null) { LOG.debug("304 response did not contain ETag matching one sent in If-None-Match"); - callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback)); + callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed); } else { - if (revalidationResponseIsTooOld(backendResponse, match.entry)) { + if (HttpCacheEntry.isNewer(match.entry, backendResponse)) { final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest( BasicRequestBuilder.copy(request).build()); scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional); - callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback)); + callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed); } else { - callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, match)); + callback = new AsyncExecCallbackWrapper(() -> updateVariantCacheEntry(backendResponse, responseDate, match), asyncExecCallback::failed); } } } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSuitability.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSuitability.java index 243148941..7a7e29055 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSuitability.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSuitability.java @@ -37,6 +37,8 @@ enum CacheSuitability { FRESH, // the cache entry is fresh and can be used to satisfy the request FRESH_ENOUGH, // the cache entry is deemed fresh enough and can be used to satisfy the request STALE, // the cache entry is stale and may be unsuitable to satisfy the request + STALE_WHILE_REVALIDATED, // the cache entry is stale but may be unsuitable to satisfy the request + // while being re-validated at the same time REVALIDATION_REQUIRED // the cache entry is stale and must not be used to satisfy the request // without revalidation diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java index ca0dbb463..2b2a2d52b 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheValidityPolicy.java @@ -146,34 +146,6 @@ class CacheValidityPolicy { return heuristicDefaultLifetime; } - public boolean isRevalidatable(final HttpCacheEntry entry) { - return entry.containsHeader(HttpHeaders.ETAG) || entry.containsHeader(HttpHeaders.LAST_MODIFIED); - } - - public boolean mayReturnStaleWhileRevalidating(final ResponseCacheControl responseCacheControl, - final HttpCacheEntry entry, final Instant now) { - if (responseCacheControl.getStaleWhileRevalidate() >= 0) { - final TimeValue staleness = getStaleness(responseCacheControl, entry, now); - if (staleness.compareTo(TimeValue.ofSeconds(responseCacheControl.getStaleWhileRevalidate())) <= 0) { - return true; - } - } - return false; - } - - public boolean mayReturnStaleIfError(final RequestCacheControl requestCacheControl, - final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry, - final Instant now) { - final TimeValue staleness = getStaleness(responseCacheControl, entry, now); - return mayReturnStaleIfError(requestCacheControl, staleness) || - mayReturnStaleIfError(responseCacheControl, staleness); - } - - private boolean mayReturnStaleIfError(final CacheControl responseCacheControl, final TimeValue staleness) { - return responseCacheControl.getStaleIfError() >= 0 && - staleness.compareTo(TimeValue.ofSeconds(responseCacheControl.getStaleIfError())) <= 0; - } - TimeValue getApparentAge(final HttpCacheEntry entry) { final Instant dateValue = entry.getInstant(); if (dateValue == null) { @@ -238,13 +210,4 @@ class CacheValidityPolicy { return TimeValue.ofSeconds(diff.getSeconds()); } - TimeValue getStaleness(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry, final Instant now) { - final TimeValue age = getCurrentAge(entry, now); - final TimeValue freshness = getFreshnessLifetime(responseCacheControl, entry); - if (age.compareTo(freshness) <= 0) { - return TimeValue.ZERO_MILLISECONDS; - } - return TimeValue.ofSeconds(age.toSeconds() - freshness.toSeconds()); - } - } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java index 69a053540..a81a59716 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java @@ -58,14 +58,16 @@ class CachedResponseSuitabilityChecker { private static final Logger LOG = LoggerFactory.getLogger(CachedResponseSuitabilityChecker.class); - private final boolean sharedCache; private final CacheValidityPolicy validityStrategy; + private final boolean sharedCache; + private final boolean staleifError; CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy, final CacheConfig config) { super(); this.validityStrategy = validityStrategy; this.sharedCache = config.isSharedCache(); + this.staleifError = config.isStaleIfErrorEnabled(); } CachedResponseSuitabilityChecker(final CacheConfig config) { @@ -173,6 +175,13 @@ class CachedResponseSuitabilityChecker { LOG.debug("The cache entry is fresh"); return CacheSuitability.FRESH; } else { + if (responseCacheControl.getStaleWhileRevalidate() > 0) { + final long stale = currentAge.compareTo(freshnessLifetime) > 0 ? currentAge.toSeconds() - freshnessLifetime.toSeconds() : 0; + if (stale < responseCacheControl.getStaleWhileRevalidate()) { + LOG.debug("The cache entry is stale but suitable while being revalidated"); + return CacheSuitability.STALE_WHILE_REVALIDATED; + } + } LOG.debug("The cache entry is stale"); return CacheSuitability.STALE; } @@ -349,4 +358,30 @@ class CachedResponseSuitabilityChecker { return true; } + public boolean isSuitableIfError(final RequestCacheControl requestCacheControl, + final ResponseCacheControl responseCacheControl, + final HttpCacheEntry entry, + final Instant now) { + // Explicit cache control + if (requestCacheControl.getStaleIfError() > 0 || responseCacheControl.getStaleIfError() > 0) { + final TimeValue currentAge = validityStrategy.getCurrentAge(entry, now); + final TimeValue freshnessLifetime = validityStrategy.getFreshnessLifetime(responseCacheControl, entry); + if (requestCacheControl.getMinFresh() > 0 && requestCacheControl.getMinFresh() < freshnessLifetime.toSeconds()) { + return false; + } + final long stale = currentAge.compareTo(freshnessLifetime) > 0 ? currentAge.toSeconds() - freshnessLifetime.toSeconds() : 0; + if (requestCacheControl.getStaleIfError() > 0 && stale < requestCacheControl.getStaleIfError()) { + return true; + } + if (responseCacheControl.getStaleIfError() > 0 && stale < responseCacheControl.getStaleIfError()) { + return true; + } + } + // Global override + if (staleifError && requestCacheControl.getStaleIfError() == -1 && responseCacheControl.getStaleIfError() == -1) { + return true; + } + return false; + } + } \ No newline at end of file diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java index 6348033a5..5e1f4bb14 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java @@ -59,6 +59,7 @@ import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; @@ -251,7 +252,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) { LOG.debug("Cache hit"); try { - return convert(generateCachedResponse(hit.entry, request, context), scope); + return convert(generateCachedResponse(request, context, hit.entry), scope); } catch (final ResourceIOException ex) { recordCacheFailure(target, request); if (!mayCallBackend(requestCacheControl)) { @@ -271,40 +272,39 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { LOG.debug("Request is not repeatable; calling backend"); return callBackend(target, request, scope, chain); } else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) { - LOG.debug("Cache entry with NOT_MODIFIED does not match the non-conditional request; calling backend"); + LOG.debug("Non-modified cache entry does not match the non-conditional request; calling backend"); return callBackend(target, request, scope, chain); - } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED || cacheSuitability == CacheSuitability.STALE) { - LOG.debug("Revalidating cache entry"); - final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry); + } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) { + LOG.debug("Revalidation required; revalidating cache entry"); try { - if (cacheRevalidator != null - && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now) - && (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) { - LOG.debug("Serving stale with asynchronous revalidation"); - final String exchangeId = ExecSupport.getNextExchangeId(); - context.setExchangeId(exchangeId); - final ExecChain.Scope fork = new ExecChain.Scope( - exchangeId, - scope.route, - scope.originalRequest, - scope.execRuntime.fork(null), - HttpClientContext.create()); - final SimpleHttpResponse response = generateCachedResponse(hit.entry, request, context); - cacheRevalidator.revalidateCacheEntry( - hit.getEntryKey(), - () -> revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, fork, chain)); - return convert(response, scope); - } - return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain); - } catch (final IOException ioex) { - if (staleIfErrorEnabled) { - if (LOG.isDebugEnabled()) { - LOG.debug("Serving stale response due to IOException and stale-if-error enabled"); - } - return convert(generateCachedResponse(hit.entry, request, context), scope); - } - return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, hit.entry, request, context, now), scope); + return revalidateCacheEntry(responseCacheControl, hit, target, request, scope, chain); + } catch (final IOException ex) { + LOG.debug(ex.getMessage(), ex); + return convert(generateGatewayTimeout(scope.clientContext), scope); } + } else if (cacheSuitability == CacheSuitability.STALE_WHILE_REVALIDATED) { + if (cacheRevalidator != null) { + LOG.debug("Serving stale with asynchronous revalidation"); + final String exchangeId = ExecSupport.getNextExchangeId(); + context.setExchangeId(exchangeId); + final ExecChain.Scope fork = new ExecChain.Scope( + exchangeId, + scope.route, + scope.originalRequest, + scope.execRuntime.fork(null), + HttpClientContext.create()); + cacheRevalidator.revalidateCacheEntry( + hit.getEntryKey(), + () -> revalidateCacheEntry(responseCacheControl, hit, target, request, fork, chain)); + final SimpleHttpResponse response = unvalidatedCacheHit(request, context, hit.entry); + return convert(response, scope); + } else { + LOG.debug("Revalidating stale cache entry (asynchronous revalidation disabled)"); + return revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, scope, chain); + } + } else if (cacheSuitability == CacheSuitability.STALE) { + LOG.debug("Revalidating stale cache entry"); + return revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, scope, chain); } else { LOG.debug("Cache entry not usable; calling backend"); return callBackend(target, request, scope, chain); @@ -313,7 +313,6 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { } ClassicHttpResponse revalidateCacheEntry( - final RequestCacheControl requestCacheControl, final ResponseCacheControl responseCacheControl, final CacheHit hit, final HttpHost target, @@ -328,7 +327,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { try { Instant responseDate = getCurrentDate(); - if (revalidationResponseIsTooOld(backendResponse, hit.entry)) { + if (HttpCacheEntry.isNewer(hit.entry, backendResponse)) { backendResponse.close(); final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest( scope.originalRequest); @@ -341,25 +340,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) { recordCacheUpdate(scope.clientContext); } - if (statusCode == HttpStatus.SC_NOT_MODIFIED) { final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate); - if (suitabilityChecker.isConditional(request) - && suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) { - return convert(responseGenerator.generateNotModifiedResponse(updated.entry), scope); - } - return convert(responseGenerator.generateResponse(request, updated.entry), scope); - } - - if (staleIfErrorAppliesTo(statusCode) - && !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate()) - && validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) { - try { - final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, hit.entry); - return convert(cachedResponse, scope); - } finally { - backendResponse.close(); - } + return convert(generateCachedResponse(request, scope.clientContext, updated.entry), scope); } return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse); } catch (final IOException | RuntimeException ex) { @@ -368,6 +351,41 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { } } + ClassicHttpResponse revalidateCacheEntryWithFallback( + final RequestCacheControl requestCacheControl, + final ResponseCacheControl responseCacheControl, + final CacheHit hit, + final HttpHost target, + final ClassicHttpRequest request, + final ExecChain.Scope scope, + final ExecChain chain) throws HttpException, IOException { + final HttpClientContext context = scope.clientContext; + final ClassicHttpResponse response; + try { + response = revalidateCacheEntry(responseCacheControl, hit, target, request, scope, chain); + } catch (final IOException ex) { + if (suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Serving stale response due to IOException and stale-if-error enabled"); + } + return convert(unvalidatedCacheHit(request, context, hit.entry), scope); + } else { + LOG.debug(ex.getMessage(), ex); + return convert(generateGatewayTimeout(context), scope); + } + } + final int status = response.getCode(); + if (staleIfErrorAppliesTo(status) && + suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Serving stale response due to {} status and stale-if-error enabled", status); + } + EntityUtils.consume(response.getEntity()); + return convert(unvalidatedCacheHit(request, context, hit.entry), scope); + } + return response; + } + ClassicHttpResponse handleBackendResponse( final HttpHost target, final ClassicHttpRequest request, @@ -518,7 +536,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { return callBackend(target, request, scope, chain); } - if (revalidationResponseIsTooOld(backendResponse, match.entry)) { + if (HttpCacheEntry.isNewer(match.entry, backendResponse)) { final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request); return callBackend(target, unconditional, scope, chain); } @@ -526,7 +544,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler { recordCacheUpdate(scope.clientContext); final CacheHit hit = responseCache.storeFromNegotiated(match, target, request, backendResponse, requestDate, responseDate); - if (shouldSendNotModifiedResponse(request, hit.entry)) { + if (shouldSendNotModifiedResponse(request, hit.entry, Instant.now())) { return convert(responseGenerator.generateNotModifiedResponse(hit.entry), scope); } else { return convert(responseGenerator.generateResponse(request, hit.entry), scope); diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java index 81bb1e4c3..a26aec2e1 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java @@ -44,7 +44,6 @@ import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.util.TimeValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,8 +87,7 @@ public class CachingExecBase { this.responseCachingPolicy = new ResponseCachingPolicy( this.cacheConfig.isSharedCache(), this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), - this.cacheConfig.isNeverCacheHTTP11ResponsesWithQuery(), - this.cacheConfig.isStaleIfErrorEnabled()); + this.cacheConfig.isNeverCacheHTTP11ResponsesWithQuery()); } /** @@ -146,31 +144,14 @@ public class CachingExecBase { } SimpleHttpResponse generateCachedResponse( - final HttpCacheEntry entry, - final HttpRequest request, - final HttpContext context) throws ResourceIOException { - final SimpleHttpResponse cachedResponse; - if (request.containsHeader(HttpHeaders.IF_NONE_MATCH) - || request.containsHeader(HttpHeaders.IF_MODIFIED_SINCE)) { - cachedResponse = responseGenerator.generateNotModifiedResponse(entry); - } else { - cachedResponse = responseGenerator.generateResponse(request, entry); - } - setResponseStatus(context, CacheResponseStatus.CACHE_HIT); - return cachedResponse; - } - - SimpleHttpResponse handleRevalidationFailure( - final RequestCacheControl requestCacheControl, - final ResponseCacheControl responseCacheControl, - final HttpCacheEntry entry, final HttpRequest request, final HttpContext context, - final Instant now) throws IOException { - if (staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)) { - return generateGatewayTimeout(context); + final HttpCacheEntry entry) throws ResourceIOException { + setResponseStatus(context, CacheResponseStatus.CACHE_HIT); + if (shouldSendNotModifiedResponse(request, entry, Instant.now())) { + return responseGenerator.generateNotModifiedResponse(entry); } else { - return unvalidatedCacheHit(request, context, entry); + return responseGenerator.generateResponse(request, entry); } } @@ -189,15 +170,6 @@ public class CachingExecBase { return cachedResponse; } - boolean staleResponseNotAllowed(final RequestCacheControl requestCacheControl, - final ResponseCacheControl responseCacheControl, - final HttpCacheEntry entry, - final Instant now) { - return responseCacheControl.isMustRevalidate() - || (cacheConfig.isSharedCache() && responseCacheControl.isProxyRevalidate()) - || explicitFreshnessRequest(requestCacheControl, responseCacheControl, entry, now); - } - boolean mayCallBackend(final RequestCacheControl requestCacheControl) { if (requestCacheControl.isOnlyIfCached()) { LOG.debug("Request marked only-if-cached"); @@ -206,22 +178,6 @@ public class CachingExecBase { return true; } - boolean explicitFreshnessRequest(final RequestCacheControl requestCacheControl, - final ResponseCacheControl responseCacheControl, - final HttpCacheEntry entry, - final Instant now) { - if (requestCacheControl.getMaxStale() >= 0) { - final TimeValue age = validityPolicy.getCurrentAge(entry, now); - final TimeValue lifetime = validityPolicy.getFreshnessLifetime(responseCacheControl, entry); - if (age.toSeconds() - lifetime.toSeconds() > requestCacheControl.getMaxStale()) { - return true; - } - } else if (requestCacheControl.getMinFresh() >= 0 || requestCacheControl.getMaxAge() >= 0) { - return true; - } - return false; - } - void setResponseStatus(final HttpContext context, final CacheResponseStatus value) { if (context != null) { context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value); @@ -245,17 +201,9 @@ public class CachingExecBase { return "0".equals(h != null ? h.getValue() : null); } - boolean revalidationResponseIsTooOld(final HttpResponse backendResponse, final HttpCacheEntry cacheEntry) { - // either backend response or cached entry did not have a valid - // Date header, so we can't tell if they are out of order - // according to the origin clock; thus we can skip the - // unconditional retry. - return HttpCacheEntry.isNewer(cacheEntry, backendResponse); - } - - boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry) { - return (suitabilityChecker.isConditional(request) - && suitabilityChecker.allConditionalsMatch(request, responseEntry, Instant.now())); + boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry, final Instant now) { + return suitabilityChecker.isConditional(request) + && suitabilityChecker.allConditionalsMatch(request, responseEntry, now); } boolean staleIfErrorAppliesTo(final int statusCode) { diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java index 67abff2ff..31dd0f3dd 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java @@ -30,7 +30,6 @@ import java.time.Duration; import java.time.Instant; import java.util.Iterator; -import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpRequest; @@ -63,14 +62,6 @@ class ResponseCachingPolicy { private final boolean neverCache1_0ResponsesWithQueryString; private final boolean neverCache1_1ResponsesWithQueryString; - /** - * A flag indicating whether serving stale cache entries is allowed when an error occurs - * while fetching a fresh response from the origin server. - * If {@code true}, stale cache entries may be served in case of errors. - * If {@code false}, stale cache entries will not be served in case of errors. - */ - private final boolean staleIfErrorEnabled; - /** * Constructs a new ResponseCachingPolicy with the specified cache policy settings and stale-if-error support. * @@ -80,20 +71,15 @@ class ResponseCachingPolicy { * {@code false} to cache if explicit cache headers are found. * @param neverCache1_1ResponsesWithQueryString {@code true} to never cache HTTP 1.1 responses with a query string, * {@code false} to cache if explicit cache headers are found. - * @param staleIfErrorEnabled {@code true} to enable the stale-if-error cache directive, which - * allows clients to receive a stale cache entry when a request - * results in an error, {@code false} to disable this feature. * @since 5.3 */ public ResponseCachingPolicy( final boolean sharedCache, final boolean neverCache1_0ResponsesWithQueryString, - final boolean neverCache1_1ResponsesWithQueryString, - final boolean staleIfErrorEnabled) { + final boolean neverCache1_1ResponsesWithQueryString) { this.sharedCache = sharedCache; this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString; this.neverCache1_1ResponsesWithQueryString = neverCache1_1ResponsesWithQueryString; - this.staleIfErrorEnabled = staleIfErrorEnabled; } /** @@ -374,28 +360,6 @@ class ResponseCachingPolicy { return DEFAULT_FRESHNESS_DURATION; // 5 minutes } - /** - * Determines whether a stale response should be served in case of an error status code in the cached response. - * This method first checks if the {@code stale-if-error} extension is enabled in the cache configuration. If it is, it - * then checks if the cached response has an error status code (500-504). If it does, it checks if the response has a - * {@code stale-while-revalidate} directive in its Cache-Control header. If it does, this method returns {@code true}, - * indicating that a stale response can be served. If not, it returns {@code false}. - * - * @return {@code true} if a stale response can be served in case of an error status code, {@code false} otherwise - */ - boolean isStaleIfErrorEnabled(final ResponseCacheControl cacheControl, final HttpCacheEntry entry) { - // Check if the stale-while-revalidate extension is enabled - if (staleIfErrorEnabled) { - // Check if the cached response has an error status code - final int statusCode = entry.getStatus(); - if (statusCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR && statusCode <= HttpStatus.SC_GATEWAY_TIMEOUT) { - // Check if the cached response has a stale-while-revalidate directive - return cacheControl.getStaleWhileRevalidate() > 0; - } - } - return false; - } - /** * Understood status codes include: * - All 2xx (Successful) status codes (200-299) diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheValidityPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheValidityPolicy.java index 11672d099..02cf52fcd 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheValidityPolicy.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheValidityPolicy.java @@ -28,7 +28,6 @@ package org.apache.hc.client5.http.impl.cache; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Instant; @@ -253,30 +252,6 @@ public class TestCacheValidityPolicy { assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry)); } - @Test - public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry( - new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())), - new BasicHeader("ETag", "somevalue")); - assertTrue(impl.isRevalidatable(entry)); - } - - @Test - public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry( - new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())), - new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now()))); - assertTrue(impl.isRevalidatable(entry)); - } - - @Test - public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry( - new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())), - new BasicHeader("Cache-Control", "public")); - assertFalse(impl.isRevalidatable(entry)); - } - @Test public void testNegativeAgeHeaderValueReturnsZero() { final Header[] headers = new Header[] { new BasicHeader("Age", "-100") }; @@ -326,103 +301,4 @@ public class TestCacheValidityPolicy { assertEquals(0, impl.getAgeValue(entry)); } - - @Test - public void testMayReturnStaleIfErrorInResponseIsTrueWithinStaleness(){ - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); - final RequestCacheControl requestCacheControl = RequestCacheControl.builder() - .build(); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .setStaleIfError(15) - .build(); - assertTrue(impl.mayReturnStaleIfError(requestCacheControl, responseCacheControl, entry, now)); - } - - @Test - public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){ - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); - final RequestCacheControl requestCacheControl = RequestCacheControl.builder() - .build(); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setStaleIfError(15) - .build(); - assertTrue(impl.mayReturnStaleIfError(requestCacheControl, responseCacheControl, entry, now)); - } - - @Test - public void testMayNotReturnStaleIfErrorInResponseAndAfterResponseWindow(){ - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); - final RequestCacheControl requestCacheControl = RequestCacheControl.builder() - .build(); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .setStaleIfError(1) - .build(); - assertFalse(impl.mayReturnStaleIfError(requestCacheControl, responseCacheControl, entry, now)); - } - - @Test - public void testMayNotReturnStaleIfErrorInResponseAndAfterRequestWindow(){ - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)), - new BasicHeader("Cache-Control", "max-age=5")); - final RequestCacheControl requestCacheControl = RequestCacheControl.builder() - .setStaleIfError(1) - .build(); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .build(); - assertFalse(impl.mayReturnStaleIfError(requestCacheControl, responseCacheControl, entry, now)); - } - - @Test - public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveIsAbsent() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setCachePublic(true) - .build(); - - assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)); - } - - @Test - public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .setStaleWhileRevalidate(15) - .build(); - - assertTrue(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)); - } - - @Test - public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() { - final Instant twentyFiveSecondsAgo = now.minusSeconds(25); - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(twentyFiveSecondsAgo))); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .setStaleWhileRevalidate(15) - .build(); - - assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)); - } - - @Test - public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() { - final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, - new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); - final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder() - .setMaxAge(5) - .setStaleWhileRevalidate(0) - .build(); - - assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)); - } } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java index 051a9d597..0b2ce981e 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java @@ -457,4 +457,105 @@ public class TestCachedResponseSuitabilityChecker { // Validate that the cache entry is not suitable for the GET request Assertions.assertEquals(CacheSuitability.MISMATCH, impl.assessSuitability(requestCacheControl, responseCacheControl, request, entry, now)); } + + @Test + public void testSuitableIfErrorRequestCacheControl() { + // Prepare a cache entry with HEAD method + entry = makeEntry(Method.GET, "/foo", + new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .build(); + + // the entry has been stale for 6 seconds + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(10) + .build(); + Assertions.assertTrue(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(5) + .build(); + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(10) + .setMinFresh(4) // should take precedence over stale-if-error + .build(); + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(-1) // not set or not valid + .build(); + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + } + + @Test + public void testSuitableIfErrorResponseCacheControl() { + // Prepare a cache entry with HEAD method + entry = makeEntry(Method.GET, "/foo", + new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .setStaleIfError(10) + .build(); + + // the entry has been stale for 6 seconds + + Assertions.assertTrue(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .setStaleIfError(5) + .build(); + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .setStaleIfError(-1) // not set or not valid + .build(); + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + } + + @Test + public void testSuitableIfErrorRequestCacheControlTakesPrecedenceOverResponseCacheControl() { + // Prepare a cache entry with HEAD method + entry = makeEntry(Method.GET, "/foo", + new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .setStaleIfError(5) + .build(); + + // the entry has been stale for 6 seconds + + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(10) + .build(); + Assertions.assertTrue(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + } + + @Test + public void testSuitableIfErrorConfigDefault() { + // Prepare a cache entry with HEAD method + entry = makeEntry(Method.GET, "/foo", + new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))); + responseCacheControl = ResponseCacheControl.builder() + .setMaxAge(5) + .build(); + impl = new CachedResponseSuitabilityChecker(CacheConfig.custom() + .setStaleIfErrorEnabled(true) + .build()); + Assertions.assertTrue(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + + requestCacheControl = RequestCacheControl.builder() + .setStaleIfError(5) + .build(); + + Assertions.assertFalse(impl.isSuitableIfError(requestCacheControl, responseCacheControl, entry, now)); + } + } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java index 1a31989d4..c7b120f15 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java @@ -589,7 +589,7 @@ public class TestCachingExecChain { Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException()); execute(req2); - Assertions.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus()); + Assertions.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE, context.getCacheResponseStatus()); } @Test @@ -1220,33 +1220,31 @@ public class TestCachingExecChain { // Create the first request and response final BasicClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "http://foo.example.com/"); - final BasicClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "http://foo.example.com/"); - final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "OK"); + final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); resp1.setEntity(HttpTestUtils.makeBody(128)); resp1.setHeader("Content-Length", "128"); - resp1.setHeader("ETag", "\"etag\""); + resp1.setHeader("ETag", "\"abc\""); resp1.setHeader("Date", DateUtils.formatStandardDate(Instant.now().minus(Duration.ofHours(10)))); - resp1.setHeader("Cache-Control", "public, max-age=-1, stale-while-revalidate=1"); + resp1.setHeader("Cache-Control", "public, stale-while-revalidate=1"); + + final BasicClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "http://foo.example.com/"); req2.addHeader("If-None-Match", "\"abc\""); - final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); + final ClassicHttpResponse resp2 = HttpTestUtils.make500Response(); // Set up the mock response chain Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1); - // Execute the first request and assert the response final ClassicHttpResponse response1 = execute(req1); - Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode()); - + Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode()); // Execute the second request and assert the response Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime); Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2); final ClassicHttpResponse response2 = execute(req2); - Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, response2.getCode()); + Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode()); - // Assert that the cache revalidator was called - Mockito.verify(cacheRevalidator, Mockito.times(1)).revalidateCacheEntry(Mockito.any(), Mockito.any()); + Mockito.verify(cacheRevalidator, Mockito.never()).revalidateCacheEntry(Mockito.any(), Mockito.any()); } @Test diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java index a89e2d898..3bfffac31 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolAllowedBehavior.java @@ -27,14 +27,11 @@ package org.apache.hc.client5.http.impl.cache; import java.io.IOException; -import java.net.SocketTimeoutException; -import java.time.Instant; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.classic.ExecChain; import org.apache.hc.client5.http.classic.ExecRuntime; import org.apache.hc.client5.http.protocol.HttpClientContext; -import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; @@ -107,29 +104,6 @@ public class TestProtocolAllowedBehavior { mockExecChain); } - @Test - public void testNonSharedCacheReturnsStaleResponseWhenRevalidationFailsForProxyRevalidate() throws Exception { - final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/"); - final Instant now = Instant.now(); - final Instant tenSecondsAgo = now.minusSeconds(10); - originResponse.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)); - originResponse.setHeader("Cache-Control","max-age=5,proxy-revalidate"); - originResponse.setHeader("Etag","\"etag\""); - - final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET","/"); - - Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse); - - execute(req1); - - Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException()); - - final HttpResponse result = execute(req2); - Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); - - Mockito.verifyNoInteractions(mockCache); - } - @Test public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate() throws Exception { final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/"); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java index 9215e5c18..eb5b9a3dc 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java @@ -68,7 +68,7 @@ public class TestResponseCachingPolicy { sixSecondsAgo = now.minusSeconds(6); tenSecondsFromNow = now.plusSeconds(10); - policy = new ResponseCachingPolicy(true, false, false, false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET","/"); response = new BasicHttpResponse(HttpStatus.SC_OK, ""); response.setHeader("Date", DateUtils.formatStandardDate(Instant.now())); @@ -78,14 +78,14 @@ public class TestResponseCachingPolicy { @Test public void testGetCacheable() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest(Method.GET, "/"); Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); } @Test public void testHeadCacheable() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest(Method.HEAD, "/"); Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); } @@ -108,7 +108,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToRequestsWithAuthorizationHeadersAreCacheableByNonSharedCache() { - policy = new ResponseCachingPolicy(false, false, false, true); + policy = new ResponseCachingPolicy(false, false, false); request = new BasicHttpRequest("GET","/"); request.setHeader("Authorization", StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q="); Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); @@ -191,7 +191,7 @@ public class TestResponseCachingPolicy { @Test public void testPlain303ResponseCodeIsNotCacheableEvenIf303CachingEnabled() { - policy = new ResponseCachingPolicy(true, false, true, true); + policy = new ResponseCachingPolicy(true, false, true); response.setCode(HttpStatus.SC_SEE_OTHER); response.removeHeaders("Expires"); Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response)); @@ -283,7 +283,7 @@ public class TestResponseCachingPolicy { @Test public void test200ResponseWithPrivateCacheControlIsCacheableByNonSharedCache() { - policy = new ResponseCachingPolicy(false, false, false, true); + policy = new ResponseCachingPolicy(false, false, false); response.setCode(HttpStatus.SC_OK); responseCacheControl = ResponseCacheControl.builder() .setCachePrivate(true) @@ -387,7 +387,7 @@ public class TestResponseCachingPolicy { @Test public void testVaryStarIsNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Vary", "*"); @@ -405,7 +405,7 @@ public class TestResponseCachingPolicy { @Test public void testIsArbitraryMethodCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new HttpOptions("http://foo.example.com/"); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); @@ -426,7 +426,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleAgeHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.addHeader("Age", "3"); @@ -446,7 +446,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleDateHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.addHeader("Date", DateUtils.formatStandardDate(now)); @@ -465,7 +465,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMalformedDateHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.addHeader("Date", "garbage"); @@ -484,7 +484,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleExpiresHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.addHeader("Expires", DateUtils.formatStandardDate(now)); @@ -515,14 +515,14 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(true, true, false, false); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response)); } @Test public void testResponsesToHEADWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(true, true, false, false); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response)); } @@ -537,7 +537,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheable() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -546,7 +546,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -555,7 +555,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -580,7 +580,7 @@ public class TestResponseCachingPolicy { @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -589,7 +589,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -608,7 +608,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -619,7 +619,7 @@ public class TestResponseCachingPolicy { @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -630,7 +630,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, false, true); + policy = new ResponseCachingPolicy(true, true, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -664,7 +664,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -674,7 +674,7 @@ public class TestResponseCachingPolicy { @Test public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, true, true); + policy = new ResponseCachingPolicy(true, true, true); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -684,7 +684,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, true, true); + policy = new ResponseCachingPolicy(true, true, true); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -703,7 +703,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() { - policy = new ResponseCachingPolicy(true, false, false, false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -713,7 +713,7 @@ public class TestResponseCachingPolicy { @Test public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, true, true); + policy = new ResponseCachingPolicy(true, true, true); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -723,7 +723,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(true, true, true, true); + policy = new ResponseCachingPolicy(true, true, true); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatStandardDate(now)); response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); @@ -744,7 +744,7 @@ public class TestResponseCachingPolicy { @Test public void headsWithQueryParametersFrom1_1OriginsVia1_0ProxiesAreCacheableWithExpires() { - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("HEAD", "/foo?s=bar"); response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); response.setVersion(HttpVersion.HTTP_1_0); @@ -783,7 +783,7 @@ public class TestResponseCachingPolicy { public void test303WithExplicitCachingHeadersWhenPermittedByConfig() { // HTTPbis working group says ok if explicitly indicated by // response headers - policy = new ResponseCachingPolicy(true, false, true, true); + policy = new ResponseCachingPolicy(true, false, true); response.setCode(HttpStatus.SC_SEE_OTHER); response.setHeader("Date", DateUtils.formatStandardDate(now)); responseCacheControl = ResponseCacheControl.builder() @@ -824,7 +824,7 @@ public class TestResponseCachingPolicy { // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false, false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); } @@ -842,7 +842,7 @@ public class TestResponseCachingPolicy { // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false, false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); responseCacheControl = ResponseCacheControl.builder() .setMaxAge(60) @@ -862,7 +862,7 @@ public class TestResponseCachingPolicy { // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false,false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); responseCacheControl = ResponseCacheControl.builder() .setMaxAge(60) @@ -882,7 +882,7 @@ public class TestResponseCachingPolicy { // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false,false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); } @@ -898,7 +898,7 @@ public class TestResponseCachingPolicy { request = new BasicHttpRequest("GET","/foo?s=bar"); // HTTPbis working group says ok if explicitly indicated by // response headers - policy = new ResponseCachingPolicy(true, false, true, true); + policy = new ResponseCachingPolicy(true, false, true); response.setCode(HttpStatus.SC_OK); response.setHeader("Date", DateUtils.formatStandardDate(now)); assertTrue(policy.isResponseCacheable(responseCacheControl, request, response)); @@ -911,7 +911,7 @@ public class TestResponseCachingPolicy { response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now())); // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false, true); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); responseCacheControl = ResponseCacheControl.builder() .setNoCache(true) @@ -926,7 +926,7 @@ public class TestResponseCachingPolicy { response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now())); // Create ResponseCachingPolicy instance and test the method - policy = new ResponseCachingPolicy(true, false, false, false); + policy = new ResponseCachingPolicy(true, false, false); request = new BasicHttpRequest("GET", "/foo"); responseCacheControl = ResponseCacheControl.builder() .setNoStore(true)