HTTPCLIENT-2277: Revised cache validation logic for conformance with the specification requirements per RFC 9111 section 4
This commit is contained in:
parent
ebae9ef7e3
commit
abb958ec27
|
@ -37,6 +37,7 @@ import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.HttpRoute;
|
import org.apache.hc.client5.http.HttpRoute;
|
||||||
import org.apache.hc.client5.http.async.AsyncExecCallback;
|
import org.apache.hc.client5.http.async.AsyncExecCallback;
|
||||||
|
@ -168,12 +169,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
|
|
||||||
static class AsyncExecCallbackWrapper implements AsyncExecCallback {
|
static class AsyncExecCallbackWrapper implements AsyncExecCallback {
|
||||||
|
|
||||||
private final AsyncExecCallback asyncExecCallback;
|
|
||||||
private final Runnable command;
|
private final Runnable command;
|
||||||
|
private final Consumer<Exception> exceptionConsumer;
|
||||||
|
|
||||||
AsyncExecCallbackWrapper(final AsyncExecCallback asyncExecCallback, final Runnable command) {
|
AsyncExecCallbackWrapper(final Runnable command, final Consumer<Exception> exceptionConsumer) {
|
||||||
this.asyncExecCallback = asyncExecCallback;
|
|
||||||
this.command = command;
|
this.command = command;
|
||||||
|
this.exceptionConsumer = exceptionConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,7 +195,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(final Exception cause) {
|
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) {
|
if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) {
|
||||||
LOG.debug("Cache hit");
|
LOG.debug("Cache hit");
|
||||||
try {
|
try {
|
||||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(hit.entry, request, context);
|
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, hit.entry);
|
||||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||||
} catch (final ResourceIOException ex) {
|
} catch (final ResourceIOException ex) {
|
||||||
recordCacheFailure(target, request);
|
recordCacheFailure(target, request);
|
||||||
|
@ -631,17 +634,47 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
LOG.debug("Request is not repeatable; calling backend");
|
LOG.debug("Request is not repeatable; calling backend");
|
||||||
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||||
} else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) {
|
} 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);
|
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||||
} else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED || cacheSuitability == CacheSuitability.STALE) {
|
} else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) {
|
||||||
LOG.debug("Revalidating cache entry");
|
LOG.debug("Revalidation required; revalidating cache entry");
|
||||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
|
revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() {
|
||||||
if (cacheRevalidator != null
|
|
||||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
|
private final AtomicBoolean committed = new AtomicBoolean();
|
||||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
|
|
||||||
|
@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");
|
LOG.debug("Serving stale with asynchronous revalidation");
|
||||||
try {
|
try {
|
||||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(hit.entry, request, context);
|
|
||||||
final String exchangeId = ExecSupport.getNextExchangeId();
|
final String exchangeId = ExecSupport.getNextExchangeId();
|
||||||
context.setExchangeId(exchangeId);
|
context.setExchangeId(exchangeId);
|
||||||
final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
|
final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
|
||||||
|
@ -656,30 +689,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
cacheRevalidator.revalidateCacheEntry(
|
cacheRevalidator.revalidateCacheEntry(
|
||||||
hit.getEntryKey(),
|
hit.getEntryKey(),
|
||||||
asyncExecCallback,
|
asyncExecCallback,
|
||||||
asyncExecCallback1 -> revalidateCacheEntry(requestCacheControl, responseCacheControl,
|
c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c));
|
||||||
hit, target, request, entityProducer, fork, chain, asyncExecCallback1));
|
final SimpleHttpResponse cacheResponse = unvalidatedCacheHit(request, context, hit.entry);
|
||||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||||
} catch (final ResourceIOException ex) {
|
} catch (final IOException ex) {
|
||||||
if (staleIfErrorEnabled) {
|
asyncExecCallback.failed(ex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
LOG.debug("Cache entry not usable; calling backend");
|
LOG.debug("Cache entry not usable; calling backend");
|
||||||
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||||
|
@ -688,7 +710,6 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
void revalidateCacheEntry(
|
void revalidateCacheEntry(
|
||||||
final RequestCacheControl requestCacheControl,
|
|
||||||
final ResponseCacheControl responseCacheControl,
|
final ResponseCacheControl responseCacheControl,
|
||||||
final CacheHit hit,
|
final CacheHit hit,
|
||||||
final HttpHost target,
|
final HttpHost target,
|
||||||
|
@ -720,17 +741,11 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed(final CacheHit updated) {
|
public void completed(final CacheHit updated) {
|
||||||
if (suitabilityChecker.isConditional(request)
|
try {
|
||||||
&& suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
|
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, scope.clientContext, updated.entry);
|
||||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updated.entry);
|
|
||||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||||
} else {
|
} catch (final ResourceIOException ex) {
|
||||||
try {
|
asyncExecCallback.failed(ex);
|
||||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
|
|
||||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
|
||||||
} catch (final ResourceIOException ex) {
|
|
||||||
asyncExecCallback.failed(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,12 +777,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
recordCacheUpdate(scope.clientContext);
|
recordCacheUpdate(scope.clientContext);
|
||||||
}
|
}
|
||||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
||||||
return new AsyncExecCallbackWrapper(asyncExecCallback, () -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate));
|
return new AsyncExecCallbackWrapper(() -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate), asyncExecCallback::failed);
|
||||||
}
|
|
||||||
if (staleIfErrorAppliesTo(statusCode)
|
|
||||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())
|
|
||||||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
|
|
||||||
return new AsyncExecCallbackWrapper(asyncExecCallback, this::triggerResponseStaleCacheEntry);
|
|
||||||
}
|
}
|
||||||
return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
|
return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
|
||||||
}
|
}
|
||||||
|
@ -780,12 +790,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
final Instant responseDate1 = getCurrentDate();
|
final Instant responseDate1 = getCurrentDate();
|
||||||
|
|
||||||
final AsyncExecCallback callback1;
|
final AsyncExecCallback callback1;
|
||||||
if (revalidationResponseIsTooOld(backendResponse1, hit.entry)) {
|
if (HttpCacheEntry.isNewer(hit.entry, backendResponse1)) {
|
||||||
|
|
||||||
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||||
BasicRequestBuilder.copy(scope.originalRequest).build());
|
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
|
@Override
|
||||||
public AsyncDataConsumer handleResponse(
|
public AsyncDataConsumer handleResponse(
|
||||||
|
@ -827,7 +837,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}));
|
}), asyncExecCallback::failed);
|
||||||
} else {
|
} else {
|
||||||
callback1 = evaluateResponse(backendResponse1, responseDate1);
|
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(
|
private void handleCacheMiss(
|
||||||
final RequestCacheControl requestCacheControl,
|
final RequestCacheControl requestCacheControl,
|
||||||
final CacheHit partialMatch,
|
final CacheHit partialMatch,
|
||||||
|
@ -954,7 +1029,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed(final CacheHit hit) {
|
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);
|
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(hit.entry);
|
||||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1055,21 +1130,21 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
|
final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
|
||||||
if (resultEtagHeader == null) {
|
if (resultEtagHeader == null) {
|
||||||
LOG.warn("304 response did not contain ETag");
|
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 {
|
} else {
|
||||||
final String resultEtag = resultEtagHeader.getValue();
|
final String resultEtag = resultEtagHeader.getValue();
|
||||||
final CacheHit match = variantMap.get(resultEtag);
|
final CacheHit match = variantMap.get(resultEtag);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
|
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 {
|
} else {
|
||||||
if (revalidationResponseIsTooOld(backendResponse, match.entry)) {
|
if (HttpCacheEntry.isNewer(match.entry, backendResponse)) {
|
||||||
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||||
BasicRequestBuilder.copy(request).build());
|
BasicRequestBuilder.copy(request).build());
|
||||||
scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
|
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 {
|
} else {
|
||||||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, match));
|
callback = new AsyncExecCallbackWrapper(() -> updateVariantCacheEntry(backendResponse, responseDate, match), asyncExecCallback::failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ enum CacheSuitability {
|
||||||
FRESH, // the cache entry is fresh and can be used to satisfy the request
|
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
|
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, // 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
|
REVALIDATION_REQUIRED
|
||||||
// the cache entry is stale and must not be used to satisfy the request
|
// the cache entry is stale and must not be used to satisfy the request
|
||||||
// without revalidation
|
// without revalidation
|
||||||
|
|
|
@ -146,34 +146,6 @@ class CacheValidityPolicy {
|
||||||
return heuristicDefaultLifetime;
|
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) {
|
TimeValue getApparentAge(final HttpCacheEntry entry) {
|
||||||
final Instant dateValue = entry.getInstant();
|
final Instant dateValue = entry.getInstant();
|
||||||
if (dateValue == null) {
|
if (dateValue == null) {
|
||||||
|
@ -238,13 +210,4 @@ class CacheValidityPolicy {
|
||||||
return TimeValue.ofSeconds(diff.getSeconds());
|
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,14 +58,16 @@ class CachedResponseSuitabilityChecker {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CachedResponseSuitabilityChecker.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CachedResponseSuitabilityChecker.class);
|
||||||
|
|
||||||
private final boolean sharedCache;
|
|
||||||
private final CacheValidityPolicy validityStrategy;
|
private final CacheValidityPolicy validityStrategy;
|
||||||
|
private final boolean sharedCache;
|
||||||
|
private final boolean staleifError;
|
||||||
|
|
||||||
CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy,
|
CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy,
|
||||||
final CacheConfig config) {
|
final CacheConfig config) {
|
||||||
super();
|
super();
|
||||||
this.validityStrategy = validityStrategy;
|
this.validityStrategy = validityStrategy;
|
||||||
this.sharedCache = config.isSharedCache();
|
this.sharedCache = config.isSharedCache();
|
||||||
|
this.staleifError = config.isStaleIfErrorEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedResponseSuitabilityChecker(final CacheConfig config) {
|
CachedResponseSuitabilityChecker(final CacheConfig config) {
|
||||||
|
@ -173,6 +175,13 @@ class CachedResponseSuitabilityChecker {
|
||||||
LOG.debug("The cache entry is fresh");
|
LOG.debug("The cache entry is fresh");
|
||||||
return CacheSuitability.FRESH;
|
return CacheSuitability.FRESH;
|
||||||
} else {
|
} 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");
|
LOG.debug("The cache entry is stale");
|
||||||
return CacheSuitability.STALE;
|
return CacheSuitability.STALE;
|
||||||
}
|
}
|
||||||
|
@ -349,4 +358,30 @@ class CachedResponseSuitabilityChecker {
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -59,6 +59,7 @@ import org.apache.hc.core5.http.HttpRequest;
|
||||||
import org.apache.hc.core5.http.HttpStatus;
|
import org.apache.hc.core5.http.HttpStatus;
|
||||||
import org.apache.hc.core5.http.HttpVersion;
|
import org.apache.hc.core5.http.HttpVersion;
|
||||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
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.entity.StringEntity;
|
||||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
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) {
|
if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) {
|
||||||
LOG.debug("Cache hit");
|
LOG.debug("Cache hit");
|
||||||
try {
|
try {
|
||||||
return convert(generateCachedResponse(hit.entry, request, context), scope);
|
return convert(generateCachedResponse(request, context, hit.entry), scope);
|
||||||
} catch (final ResourceIOException ex) {
|
} catch (final ResourceIOException ex) {
|
||||||
recordCacheFailure(target, request);
|
recordCacheFailure(target, request);
|
||||||
if (!mayCallBackend(requestCacheControl)) {
|
if (!mayCallBackend(requestCacheControl)) {
|
||||||
|
@ -271,40 +272,39 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
LOG.debug("Request is not repeatable; calling backend");
|
LOG.debug("Request is not repeatable; calling backend");
|
||||||
return callBackend(target, request, scope, chain);
|
return callBackend(target, request, scope, chain);
|
||||||
} else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) {
|
} 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);
|
return callBackend(target, request, scope, chain);
|
||||||
} else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED || cacheSuitability == CacheSuitability.STALE) {
|
} else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) {
|
||||||
LOG.debug("Revalidating cache entry");
|
LOG.debug("Revalidation required; revalidating cache entry");
|
||||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
|
|
||||||
try {
|
try {
|
||||||
if (cacheRevalidator != null
|
return revalidateCacheEntry(responseCacheControl, hit, target, request, scope, chain);
|
||||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
|
} catch (final IOException ex) {
|
||||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
|
LOG.debug(ex.getMessage(), ex);
|
||||||
LOG.debug("Serving stale with asynchronous revalidation");
|
return convert(generateGatewayTimeout(scope.clientContext), scope);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
} 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 {
|
} else {
|
||||||
LOG.debug("Cache entry not usable; calling backend");
|
LOG.debug("Cache entry not usable; calling backend");
|
||||||
return callBackend(target, request, scope, chain);
|
return callBackend(target, request, scope, chain);
|
||||||
|
@ -313,7 +313,6 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassicHttpResponse revalidateCacheEntry(
|
ClassicHttpResponse revalidateCacheEntry(
|
||||||
final RequestCacheControl requestCacheControl,
|
|
||||||
final ResponseCacheControl responseCacheControl,
|
final ResponseCacheControl responseCacheControl,
|
||||||
final CacheHit hit,
|
final CacheHit hit,
|
||||||
final HttpHost target,
|
final HttpHost target,
|
||||||
|
@ -328,7 +327,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
try {
|
try {
|
||||||
Instant responseDate = getCurrentDate();
|
Instant responseDate = getCurrentDate();
|
||||||
|
|
||||||
if (revalidationResponseIsTooOld(backendResponse, hit.entry)) {
|
if (HttpCacheEntry.isNewer(hit.entry, backendResponse)) {
|
||||||
backendResponse.close();
|
backendResponse.close();
|
||||||
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||||
scope.originalRequest);
|
scope.originalRequest);
|
||||||
|
@ -341,25 +340,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
|
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
|
||||||
recordCacheUpdate(scope.clientContext);
|
recordCacheUpdate(scope.clientContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
||||||
final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate);
|
final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate);
|
||||||
if (suitabilityChecker.isConditional(request)
|
return convert(generateCachedResponse(request, scope.clientContext, updated.entry), scope);
|
||||||
&& 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 handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
|
return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
|
||||||
} catch (final IOException | RuntimeException ex) {
|
} 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(
|
ClassicHttpResponse handleBackendResponse(
|
||||||
final HttpHost target,
|
final HttpHost target,
|
||||||
final ClassicHttpRequest request,
|
final ClassicHttpRequest request,
|
||||||
|
@ -518,7 +536,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
return callBackend(target, request, scope, chain);
|
return callBackend(target, request, scope, chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revalidationResponseIsTooOld(backendResponse, match.entry)) {
|
if (HttpCacheEntry.isNewer(match.entry, backendResponse)) {
|
||||||
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
|
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
|
||||||
return callBackend(target, unconditional, scope, chain);
|
return callBackend(target, unconditional, scope, chain);
|
||||||
}
|
}
|
||||||
|
@ -526,7 +544,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
recordCacheUpdate(scope.clientContext);
|
recordCacheUpdate(scope.clientContext);
|
||||||
|
|
||||||
final CacheHit hit = responseCache.storeFromNegotiated(match, target, request, backendResponse, requestDate, responseDate);
|
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);
|
return convert(responseGenerator.generateNotModifiedResponse(hit.entry), scope);
|
||||||
} else {
|
} else {
|
||||||
return convert(responseGenerator.generateResponse(request, hit.entry), scope);
|
return convert(responseGenerator.generateResponse(request, hit.entry), scope);
|
||||||
|
|
|
@ -44,7 +44,6 @@ import org.apache.hc.core5.http.HttpResponse;
|
||||||
import org.apache.hc.core5.http.HttpStatus;
|
import org.apache.hc.core5.http.HttpStatus;
|
||||||
import org.apache.hc.core5.http.Method;
|
import org.apache.hc.core5.http.Method;
|
||||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||||
import org.apache.hc.core5.util.TimeValue;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -88,8 +87,7 @@ public class CachingExecBase {
|
||||||
this.responseCachingPolicy = new ResponseCachingPolicy(
|
this.responseCachingPolicy = new ResponseCachingPolicy(
|
||||||
this.cacheConfig.isSharedCache(),
|
this.cacheConfig.isSharedCache(),
|
||||||
this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(),
|
this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(),
|
||||||
this.cacheConfig.isNeverCacheHTTP11ResponsesWithQuery(),
|
this.cacheConfig.isNeverCacheHTTP11ResponsesWithQuery());
|
||||||
this.cacheConfig.isStaleIfErrorEnabled());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,31 +144,14 @@ public class CachingExecBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleHttpResponse generateCachedResponse(
|
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 HttpRequest request,
|
||||||
final HttpContext context,
|
final HttpContext context,
|
||||||
final Instant now) throws IOException {
|
final HttpCacheEntry entry) throws ResourceIOException {
|
||||||
if (staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)) {
|
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
|
||||||
return generateGatewayTimeout(context);
|
if (shouldSendNotModifiedResponse(request, entry, Instant.now())) {
|
||||||
|
return responseGenerator.generateNotModifiedResponse(entry);
|
||||||
} else {
|
} else {
|
||||||
return unvalidatedCacheHit(request, context, entry);
|
return responseGenerator.generateResponse(request, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +170,6 @@ public class CachingExecBase {
|
||||||
return cachedResponse;
|
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) {
|
boolean mayCallBackend(final RequestCacheControl requestCacheControl) {
|
||||||
if (requestCacheControl.isOnlyIfCached()) {
|
if (requestCacheControl.isOnlyIfCached()) {
|
||||||
LOG.debug("Request marked only-if-cached");
|
LOG.debug("Request marked only-if-cached");
|
||||||
|
@ -206,22 +178,6 @@ public class CachingExecBase {
|
||||||
return true;
|
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) {
|
void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
|
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
|
||||||
|
@ -245,17 +201,9 @@ public class CachingExecBase {
|
||||||
return "0".equals(h != null ? h.getValue() : null);
|
return "0".equals(h != null ? h.getValue() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean revalidationResponseIsTooOld(final HttpResponse backendResponse, final HttpCacheEntry cacheEntry) {
|
boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry, final Instant now) {
|
||||||
// either backend response or cached entry did not have a valid
|
return suitabilityChecker.isConditional(request)
|
||||||
// Date header, so we can't tell if they are out of order
|
&& suitabilityChecker.allConditionalsMatch(request, responseEntry, now);
|
||||||
// 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 staleIfErrorAppliesTo(final int statusCode) {
|
boolean staleIfErrorAppliesTo(final int statusCode) {
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
import org.apache.hc.client5.http.utils.DateUtils;
|
||||||
import org.apache.hc.core5.http.HttpHeaders;
|
import org.apache.hc.core5.http.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
|
@ -63,14 +62,6 @@ class ResponseCachingPolicy {
|
||||||
private final boolean neverCache1_0ResponsesWithQueryString;
|
private final boolean neverCache1_0ResponsesWithQueryString;
|
||||||
private final boolean neverCache1_1ResponsesWithQueryString;
|
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.
|
* 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.
|
* {@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,
|
* @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.
|
* {@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
|
* @since 5.3
|
||||||
*/
|
*/
|
||||||
public ResponseCachingPolicy(
|
public ResponseCachingPolicy(
|
||||||
final boolean sharedCache,
|
final boolean sharedCache,
|
||||||
final boolean neverCache1_0ResponsesWithQueryString,
|
final boolean neverCache1_0ResponsesWithQueryString,
|
||||||
final boolean neverCache1_1ResponsesWithQueryString,
|
final boolean neverCache1_1ResponsesWithQueryString) {
|
||||||
final boolean staleIfErrorEnabled) {
|
|
||||||
this.sharedCache = sharedCache;
|
this.sharedCache = sharedCache;
|
||||||
this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString;
|
this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString;
|
||||||
this.neverCache1_1ResponsesWithQueryString = neverCache1_1ResponsesWithQueryString;
|
this.neverCache1_1ResponsesWithQueryString = neverCache1_1ResponsesWithQueryString;
|
||||||
this.staleIfErrorEnabled = staleIfErrorEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -374,28 +360,6 @@ class ResponseCachingPolicy {
|
||||||
return DEFAULT_FRESHNESS_DURATION; // 5 minutes
|
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:
|
* Understood status codes include:
|
||||||
* - All 2xx (Successful) status codes (200-299)
|
* - All 2xx (Successful) status codes (200-299)
|
||||||
|
|
|
@ -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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -253,30 +252,6 @@ public class TestCacheValidityPolicy {
|
||||||
assertEquals(defaultFreshness, impl.getHeuristicFreshnessLifetime(entry));
|
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
|
@Test
|
||||||
public void testNegativeAgeHeaderValueReturnsZero() {
|
public void testNegativeAgeHeaderValueReturnsZero() {
|
||||||
final Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
|
final Header[] headers = new Header[] { new BasicHeader("Age", "-100") };
|
||||||
|
@ -326,103 +301,4 @@ public class TestCacheValidityPolicy {
|
||||||
assertEquals(0, impl.getAgeValue(entry));
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,4 +457,105 @@ public class TestCachedResponseSuitabilityChecker {
|
||||||
// Validate that the cache entry is not suitable for the GET request
|
// Validate that the cache entry is not suitable for the GET request
|
||||||
Assertions.assertEquals(CacheSuitability.MISMATCH, impl.assessSuitability(requestCacheControl, responseCacheControl, request, entry, now));
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -589,7 +589,7 @@ public class TestCachingExecChain {
|
||||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
|
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
|
||||||
|
|
||||||
execute(req2);
|
execute(req2);
|
||||||
Assertions.assertEquals(CacheResponseStatus.CACHE_HIT, context.getCacheResponseStatus());
|
Assertions.assertEquals(CacheResponseStatus.CACHE_MODULE_RESPONSE, context.getCacheResponseStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1220,33 +1220,31 @@ public class TestCachingExecChain {
|
||||||
|
|
||||||
// Create the first request and response
|
// Create the first request and response
|
||||||
final BasicClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "http://foo.example.com/");
|
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_OK, "OK");
|
||||||
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "OK");
|
|
||||||
resp1.setEntity(HttpTestUtils.makeBody(128));
|
resp1.setEntity(HttpTestUtils.makeBody(128));
|
||||||
resp1.setHeader("Content-Length", "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("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\"");
|
req2.addHeader("If-None-Match", "\"abc\"");
|
||||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
|
||||||
|
|
||||||
// Set up the mock response chain
|
// Set up the mock response chain
|
||||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||||
|
|
||||||
|
|
||||||
// Execute the first request and assert the response
|
// Execute the first request and assert the response
|
||||||
final ClassicHttpResponse response1 = execute(req1);
|
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
|
// Execute the second request and assert the response
|
||||||
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
||||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||||
final ClassicHttpResponse response2 = execute(req2);
|
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.never()).revalidateCacheEntry(Mockito.any(), Mockito.any());
|
||||||
Mockito.verify(cacheRevalidator, Mockito.times(1)).revalidateCacheEntry(Mockito.any(), Mockito.any());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -27,14 +27,11 @@
|
||||||
package org.apache.hc.client5.http.impl.cache;
|
package org.apache.hc.client5.http.impl.cache;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.HttpRoute;
|
||||||
import org.apache.hc.client5.http.classic.ExecChain;
|
import org.apache.hc.client5.http.classic.ExecChain;
|
||||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
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.ClassicHttpRequest;
|
||||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
import org.apache.hc.core5.http.HttpException;
|
import org.apache.hc.core5.http.HttpException;
|
||||||
|
@ -107,29 +104,6 @@ public class TestProtocolAllowedBehavior {
|
||||||
mockExecChain);
|
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
|
@Test
|
||||||
public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate() throws Exception {
|
public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate() throws Exception {
|
||||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
|
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET","/");
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class TestResponseCachingPolicy {
|
||||||
sixSecondsAgo = now.minusSeconds(6);
|
sixSecondsAgo = now.minusSeconds(6);
|
||||||
tenSecondsFromNow = now.plusSeconds(10);
|
tenSecondsFromNow = now.plusSeconds(10);
|
||||||
|
|
||||||
policy = new ResponseCachingPolicy(true, false, false, false);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest("GET","/");
|
request = new BasicHttpRequest("GET","/");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
response.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
||||||
|
@ -78,14 +78,14 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCacheable() {
|
public void testGetCacheable() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest(Method.GET, "/");
|
request = new BasicHttpRequest(Method.GET, "/");
|
||||||
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHeadCacheable() {
|
public void testHeadCacheable() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest(Method.HEAD, "/");
|
request = new BasicHttpRequest(Method.HEAD, "/");
|
||||||
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToRequestsWithAuthorizationHeadersAreCacheableByNonSharedCache() {
|
public void testResponsesToRequestsWithAuthorizationHeadersAreCacheableByNonSharedCache() {
|
||||||
policy = new ResponseCachingPolicy(false, false, false, true);
|
policy = new ResponseCachingPolicy(false, false, false);
|
||||||
request = new BasicHttpRequest("GET","/");
|
request = new BasicHttpRequest("GET","/");
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " dXNlcjpwYXNzd2Q=");
|
||||||
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
|
@ -191,7 +191,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPlain303ResponseCodeIsNotCacheableEvenIf303CachingEnabled() {
|
public void testPlain303ResponseCodeIsNotCacheableEvenIf303CachingEnabled() {
|
||||||
policy = new ResponseCachingPolicy(true, false, true, true);
|
policy = new ResponseCachingPolicy(true, false, true);
|
||||||
response.setCode(HttpStatus.SC_SEE_OTHER);
|
response.setCode(HttpStatus.SC_SEE_OTHER);
|
||||||
response.removeHeaders("Expires");
|
response.removeHeaders("Expires");
|
||||||
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
|
@ -283,7 +283,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test200ResponseWithPrivateCacheControlIsCacheableByNonSharedCache() {
|
public void test200ResponseWithPrivateCacheControlIsCacheableByNonSharedCache() {
|
||||||
policy = new ResponseCachingPolicy(false, false, false, true);
|
policy = new ResponseCachingPolicy(false, false, false);
|
||||||
response.setCode(HttpStatus.SC_OK);
|
response.setCode(HttpStatus.SC_OK);
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
.setCachePrivate(true)
|
.setCachePrivate(true)
|
||||||
|
@ -387,7 +387,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVaryStarIsNotCacheableUsingSharedPublicCache() {
|
public void testVaryStarIsNotCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
response.setHeader("Vary", "*");
|
response.setHeader("Vary", "*");
|
||||||
|
@ -405,7 +405,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsArbitraryMethodCacheableUsingSharedPublicCache() {
|
public void testIsArbitraryMethodCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request = new HttpOptions("http://foo.example.com/");
|
request = new HttpOptions("http://foo.example.com/");
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
|
@ -426,7 +426,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesWithMultipleAgeHeadersAreNotCacheableUsingSharedPublicCache() {
|
public void testResponsesWithMultipleAgeHeadersAreNotCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
response.addHeader("Age", "3");
|
response.addHeader("Age", "3");
|
||||||
|
@ -446,7 +446,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesWithMultipleDateHeadersAreNotCacheableUsingSharedPublicCache() {
|
public void testResponsesWithMultipleDateHeadersAreNotCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
response.addHeader("Date", DateUtils.formatStandardDate(now));
|
response.addHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
|
@ -465,7 +465,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesWithMalformedDateHeadersAreNotCacheableUsingSharedPublicCache() {
|
public void testResponsesWithMalformedDateHeadersAreNotCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
response.addHeader("Date", "garbage");
|
response.addHeader("Date", "garbage");
|
||||||
|
@ -484,7 +484,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesWithMultipleExpiresHeadersAreNotCacheableUsingSharedPublicCache() {
|
public void testResponsesWithMultipleExpiresHeadersAreNotCacheableUsingSharedPublicCache() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
|
|
||||||
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
request.setHeader("Authorization", StandardAuthScheme.BASIC + " QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
|
||||||
response.addHeader("Expires", DateUtils.formatStandardDate(now));
|
response.addHeader("Expires", DateUtils.formatStandardDate(now));
|
||||||
|
@ -515,14 +515,14 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() {
|
public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, false);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToHEADWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() {
|
public void testResponsesToHEADWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, false);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
Assertions.assertFalse(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
@ -537,7 +537,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheable() {
|
public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheable() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -546,7 +546,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
|
public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -555,7 +555,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
|
public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -580,7 +580,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() {
|
public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -589,7 +589,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() {
|
public void headsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -608,7 +608,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() {
|
public void headsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -619,7 +619,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() {
|
public void getsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -630,7 +630,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() {
|
public void headsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, false, true);
|
policy = new ResponseCachingPolicy(true, true, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -664,7 +664,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() {
|
public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, true);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -674,7 +674,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
|
public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, true, true);
|
policy = new ResponseCachingPolicy(true, true, true);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -684,7 +684,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
|
public void headsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, true, true);
|
policy = new ResponseCachingPolicy(true, true, true);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -703,7 +703,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() {
|
public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, false, false, false);
|
policy = new ResponseCachingPolicy(true, false, false);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -713,7 +713,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
|
public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, true, true);
|
policy = new ResponseCachingPolicy(true, true, true);
|
||||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -723,7 +723,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
|
public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
|
||||||
policy = new ResponseCachingPolicy(true, true, true, true);
|
policy = new ResponseCachingPolicy(true, true, true);
|
||||||
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow));
|
||||||
|
@ -744,7 +744,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headsWithQueryParametersFrom1_1OriginsVia1_0ProxiesAreCacheableWithExpires() {
|
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");
|
request = new BasicHttpRequest("HEAD", "/foo?s=bar");
|
||||||
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||||
response.setVersion(HttpVersion.HTTP_1_0);
|
response.setVersion(HttpVersion.HTTP_1_0);
|
||||||
|
@ -783,7 +783,7 @@ public class TestResponseCachingPolicy {
|
||||||
public void test303WithExplicitCachingHeadersWhenPermittedByConfig() {
|
public void test303WithExplicitCachingHeadersWhenPermittedByConfig() {
|
||||||
// HTTPbis working group says ok if explicitly indicated by
|
// HTTPbis working group says ok if explicitly indicated by
|
||||||
// response headers
|
// response headers
|
||||||
policy = new ResponseCachingPolicy(true, false, true, true);
|
policy = new ResponseCachingPolicy(true, false, true);
|
||||||
response.setCode(HttpStatus.SC_SEE_OTHER);
|
response.setCode(HttpStatus.SC_SEE_OTHER);
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
|
@ -824,7 +824,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
@ -842,7 +842,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
.setMaxAge(60)
|
.setMaxAge(60)
|
||||||
|
@ -862,7 +862,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
.setMaxAge(60)
|
.setMaxAge(60)
|
||||||
|
@ -882,7 +882,7 @@ public class TestResponseCachingPolicy {
|
||||||
|
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
}
|
}
|
||||||
|
@ -898,7 +898,7 @@ public class TestResponseCachingPolicy {
|
||||||
request = new BasicHttpRequest("GET","/foo?s=bar");
|
request = new BasicHttpRequest("GET","/foo?s=bar");
|
||||||
// HTTPbis working group says ok if explicitly indicated by
|
// HTTPbis working group says ok if explicitly indicated by
|
||||||
// response headers
|
// response headers
|
||||||
policy = new ResponseCachingPolicy(true, false, true, true);
|
policy = new ResponseCachingPolicy(true, false, true);
|
||||||
response.setCode(HttpStatus.SC_OK);
|
response.setCode(HttpStatus.SC_OK);
|
||||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
assertTrue(policy.isResponseCacheable(responseCacheControl, request, response));
|
||||||
|
@ -911,7 +911,7 @@ public class TestResponseCachingPolicy {
|
||||||
response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
|
response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
.setNoCache(true)
|
.setNoCache(true)
|
||||||
|
@ -926,7 +926,7 @@ public class TestResponseCachingPolicy {
|
||||||
response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
|
response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
|
||||||
|
|
||||||
// Create ResponseCachingPolicy instance and test the method
|
// 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");
|
request = new BasicHttpRequest("GET", "/foo");
|
||||||
responseCacheControl = ResponseCacheControl.builder()
|
responseCacheControl = ResponseCacheControl.builder()
|
||||||
.setNoStore(true)
|
.setNoStore(true)
|
||||||
|
|
Loading…
Reference in New Issue