Parse Cache-Control request and response headers only once

This commit is contained in:
Oleg Kalnichevski 2023-05-21 12:25:38 +02:00
parent cd2930af1f
commit fbed77880b
15 changed files with 764 additions and 745 deletions

View File

@ -239,7 +239,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
requestCompliance.makeRequestCompliant(request);
request.addHeader(HttpHeaders.VIA,via);
if (!cacheableRequestPolicy.isServableFromCache(request)) {
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (!cacheableRequestPolicy.isServableFromCache(requestCacheControl, request)) {
LOG.debug("Request is not servable from cache");
operation.setDependency(responseCache.flushCacheEntriesInvalidatedByRequest(target, request, new FutureCallback<Boolean>() {
@ -266,9 +268,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
public void completed(final HttpCacheEntry entry) {
if (entry == null) {
LOG.debug("Cache miss");
handleCacheMiss(target, request, entityProducer, scope, chain, asyncExecCallback);
handleCacheMiss(requestCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback);
} else {
handleCacheHit(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
handleCacheHit(requestCacheControl, responseCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback, entry);
}
}
@ -488,7 +491,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
});
final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(backendResponse);
final boolean cacheable = responseCachingPolicy.isResponseCacheable(responseCacheControl, request, backendResponse);
if (cacheable) {
cachingConsumerRef.set(new CachingAsyncDataConsumer(asyncExecCallback, backendResponse, entityDetails));
storeRequestIfModifiedSinceFor304Response(request, backendResponse);
@ -612,6 +616,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
private void handleCacheHit(
final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl,
final HttpHost target,
final HttpRequest request,
final AsyncEntityProducer entityProducer,
@ -623,28 +629,30 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
recordCacheHit(target, request);
final Instant now = getCurrentDate();
if (requestContainsNoCacheDirective(request)) {
if (requestCacheControl.isNoCache()) {
// Revalidate with the server due to no-cache directive in response
if (LOG.isDebugEnabled()) {
LOG.debug("Revalidating with server due to no-cache directive in response.");
}
revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
revalidateCacheEntry(requestCacheControl, responseCacheControl,
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
return;
}
if (suitabilityChecker.canCachedResponseBeUsed(request, entry, now)) {
if (responseCachingPolicy.responseContainsNoCacheDirective(entry)) {
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
// Revalidate with the server due to no-cache directive in response
revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
revalidateCacheEntry(requestCacheControl, responseCacheControl,
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
return;
}
LOG.debug("Cache hit");
try {
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
recordCacheFailure(target, request);
if (!mayCallBackend(request)) {
if (!mayCallBackend(requestCacheControl)) {
final SimpleHttpResponse cacheResponse = generateGatewayTimeout(context);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} else {
@ -656,20 +664,20 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
}
}
} else if (!mayCallBackend(request)) {
} else if (!mayCallBackend(requestCacheControl)) {
LOG.debug("Cache entry not suitable but only-if-cached requested");
final SimpleHttpResponse cacheResponse = generateGatewayTimeout(context);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
LOG.debug("Revalidating cache entry");
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(entry);
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
if (cacheRevalidator != null
&& !staleResponseNotAllowed(request, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)
|| staleIfErrorEnabled) {
LOG.debug("Serving stale with asynchronous revalidation");
try {
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
final String exchangeId = ExecSupport.getNextExchangeId();
context.setExchangeId(exchangeId);
final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
@ -684,7 +692,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
cacheRevalidator.revalidateCacheEntry(
responseCache.generateKey(target, request, entry),
asyncExecCallback,
asyncExecCallback1 -> revalidateCacheEntry(target, request, entityProducer, fork, chain, asyncExecCallback1, entry));
asyncExecCallback1 -> revalidateCacheEntry(requestCacheControl, responseCacheControl,
target, request, entityProducer, fork, chain, asyncExecCallback1, entry));
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex) {
if (staleIfErrorEnabled) {
@ -692,7 +701,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
}
try {
final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl,
request, context, entry, now);
triggerResponse(cacheResponse, scope, asyncExecCallback);
} catch (final ResourceIOException ex2) {
if (LOG.isDebugEnabled()) {
@ -705,7 +715,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
}
} else {
revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
revalidateCacheEntry(requestCacheControl, responseCacheControl,
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
}
} else {
LOG.debug("Cache entry not usable; calling backend");
@ -714,6 +725,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
void revalidateCacheEntry(
final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl,
final HttpHost target,
final HttpRequest request,
final AsyncEntityProducer entityProducer,
@ -723,6 +736,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
final HttpCacheEntry cacheEntry) {
final Instant requestDate = getCurrentDate();
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
responseCacheControl,
BasicRequestBuilder.copy(scope.originalRequest).build(),
cacheEntry);
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
@ -791,8 +805,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
return new AsyncExecCallbackWrapper(asyncExecCallback, () -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate));
}
if (staleIfErrorAppliesTo(statusCode)
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
return new AsyncExecCallbackWrapper(asyncExecCallback, this::triggerResponseStaleCacheEntry);
}
return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
@ -897,6 +911,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
}
private void handleCacheMiss(
final RequestCacheControl requestCacheControl,
final HttpHost target,
final HttpRequest request,
final AsyncEntityProducer entityProducer,
@ -905,7 +920,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
final AsyncExecCallback asyncExecCallback) {
recordCacheMiss(target, request);
if (mayCallBackend(request)) {
if (mayCallBackend(requestCacheControl)) {
final CancellableDependency operation = scope.cancellableDependency;
operation.setDependency(responseCache.getVariantCacheEntriesWithEtags(
target,

View File

@ -34,7 +34,6 @@ import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.util.TimeValue;
class CacheValidityPolicy {
@ -50,8 +49,8 @@ class CacheValidityPolicy {
return TimeValue.ofSeconds(getCorrectedInitialAge(entry).toSeconds() + getResidentTime(entry, now).toSeconds());
}
public TimeValue getFreshnessLifetime(final HttpCacheEntry entry) {
final long maxAge = getMaxAge(entry);
public TimeValue getFreshnessLifetime(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry) {
final long maxAge = getMaxAge(responseCacheControl);
if (maxAge > -1) {
return TimeValue.ofSeconds(maxAge);
}
@ -69,8 +68,9 @@ class CacheValidityPolicy {
return TimeValue.ofSeconds(diff.getSeconds());
}
public boolean isResponseFresh(final HttpCacheEntry entry, final Instant now) {
return getCurrentAge(entry, now).compareTo(getFreshnessLifetime(entry)) == -1;
public boolean isResponseFresh(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry,
final Instant now) {
return getCurrentAge(entry, now).compareTo(getFreshnessLifetime(responseCacheControl, entry)) == -1;
}
/**
@ -114,36 +114,28 @@ class CacheValidityPolicy {
|| entry.getFirstHeader(HttpHeaders.LAST_MODIFIED) != null;
}
public boolean mustRevalidate(final HttpCacheEntry entry) {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
return cacheControl.isMustRevalidate();
}
public boolean proxyRevalidate(final HttpCacheEntry entry) {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
return cacheControl.isProxyRevalidate();
}
public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, final Instant now) {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
if (cacheControl.getStaleWhileRevalidate() >= 0) {
final TimeValue staleness = getStaleness(entry, now);
if (staleness.compareTo(TimeValue.ofSeconds(cacheControl.getStaleWhileRevalidate())) <= 0) {
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 HttpRequest request, final HttpCacheEntry entry, final Instant now) {
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
final TimeValue staleness = getStaleness(entry, now);
return mayReturnStaleIfError(requestCacheControl, staleness) || mayReturnStaleIfError(cacheControl, staleness);
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 cacheControl, final TimeValue staleness) {
return cacheControl.getStaleIfError() >= 0 && staleness.compareTo(TimeValue.ofSeconds(cacheControl.getStaleIfError())) <= 0;
private boolean mayReturnStaleIfError(final CacheControl responseCacheControl, final TimeValue staleness) {
return responseCacheControl.getStaleIfError() >= 0 &&
staleness.compareTo(TimeValue.ofSeconds(responseCacheControl.getStaleIfError())) <= 0;
}
/**
@ -222,10 +214,9 @@ class CacheValidityPolicy {
}
protected long getMaxAge(final HttpCacheEntry entry) {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
final long maxAge = cacheControl.getMaxAge();
final long sharedMaxAge = cacheControl.getSharedMaxAge();
protected long getMaxAge(final ResponseCacheControl responseCacheControl) {
final long maxAge = responseCacheControl.getMaxAge();
final long sharedMaxAge = responseCacheControl.getSharedMaxAge();
if (sharedMaxAge == -1) {
return maxAge;
} else if (maxAge == -1) {
@ -235,9 +226,9 @@ class CacheValidityPolicy {
}
}
public TimeValue getStaleness(final HttpCacheEntry entry, final Instant now) {
public TimeValue getStaleness(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry, final Instant now) {
final TimeValue age = getCurrentAge(entry, now);
final TimeValue freshness = getFreshnessLifetime(entry);
final TimeValue freshness = getFreshnessLifetime(responseCacheControl, entry);
if (age.compareTo(freshness) <= 0) {
return TimeValue.ZERO_MILLISECONDS;
}

View File

@ -48,7 +48,7 @@ class CacheableRequestPolicy {
* an HttpRequest
* @return boolean Is it possible to serve this request from cache
*/
public boolean isServableFromCache(final HttpRequest request) {
public boolean isServableFromCache(final RequestCacheControl cacheControl, final HttpRequest request) {
final String method = request.getMethod();
final ProtocolVersion pv = request.getVersion() != null ? request.getVersion() : HttpVersion.DEFAULT;
@ -69,7 +69,6 @@ class CacheableRequestPolicy {
return false;
}
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (cacheControl.isNoStore()) {
LOG.debug("Request with no-store is not serveable from cache");
return false;

View File

@ -70,56 +70,50 @@ class CachedResponseSuitabilityChecker {
this(new CacheValidityPolicy(), config);
}
private boolean isFreshEnough(final HttpCacheEntry entry, final HttpRequest request, final Instant now) {
if (validityStrategy.isResponseFresh(entry, now)) {
private boolean isFreshEnough(final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry,
final Instant now) {
if (validityStrategy.isResponseFresh(responseCacheControl, entry, now)) {
return true;
}
if (useHeuristicCaching &&
validityStrategy.isResponseHeuristicallyFresh(entry, now, heuristicCoefficient, heuristicDefaultLifetime)) {
return true;
}
if (originInsistsOnFreshness(entry)) {
if (originInsistsOnFreshness(responseCacheControl)) {
return false;
}
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (cacheControl.getMaxStale() == -1) {
if (requestCacheControl.getMaxStale() == -1) {
return false;
}
return (cacheControl.getMaxStale() > validityStrategy.getStaleness(entry, now).toSeconds());
return (requestCacheControl.getMaxStale() > validityStrategy.getStaleness(responseCacheControl, entry, now).toSeconds());
}
private boolean originInsistsOnFreshness(final HttpCacheEntry entry) {
if (validityStrategy.mustRevalidate(entry)) {
private boolean originInsistsOnFreshness(final ResponseCacheControl responseCacheControl) {
if (responseCacheControl.isMustRevalidate()) {
return true;
}
if (!sharedCache) {
return false;
}
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
return cacheControl.isProxyRevalidate() || cacheControl.getSharedMaxAge() >= 0;
return responseCacheControl.isProxyRevalidate() || responseCacheControl.getSharedMaxAge() >= 0;
}
/**
* Determine if I can utilize a {@link HttpCacheEntry} to respond to the given
* {@link HttpRequest}
*
* @param request
* {@link HttpRequest}
* @param entry
* {@link HttpCacheEntry}
* @param now
* Right now in time
* @return boolean yes/no answer
* @since 5.3
*/
public boolean canCachedResponseBeUsed(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
public boolean canCachedResponseBeUsed(final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl, final HttpRequest request,
final HttpCacheEntry entry, final Instant now) {
if (isGetRequestWithHeadCacheEntry(request, entry)) {
LOG.debug("Cache entry created by HEAD request cannot be used to serve GET request");
return false;
}
if (!isFreshEnough(entry, request, now)) {
if (!isFreshEnough(requestCacheControl, responseCacheControl, entry, now)) {
LOG.debug("Cache entry is not fresh enough");
return false;
}
@ -149,38 +143,37 @@ class CachedResponseSuitabilityChecker {
"request method, entity or a 204 response");
return false;
}
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (cacheControl.isNoCache()) {
if (requestCacheControl.isNoCache()) {
LOG.debug("Response contained NO CACHE directive, cache was not suitable");
return false;
}
if (cacheControl.isNoStore()) {
if (requestCacheControl.isNoStore()) {
LOG.debug("Response contained NO STORE directive, cache was not suitable");
return false;
}
if (cacheControl.getMaxAge() >= 0) {
if (validityStrategy.getCurrentAge(entry, now).toSeconds() > cacheControl.getMaxAge()) {
if (requestCacheControl.getMaxAge() >= 0) {
if (validityStrategy.getCurrentAge(entry, now).toSeconds() > requestCacheControl.getMaxAge()) {
LOG.debug("Response from cache was not suitable due to max age");
return false;
}
}
if (cacheControl.getMaxStale() >= 0) {
if (validityStrategy.getFreshnessLifetime(entry).toSeconds() > cacheControl.getMaxStale()) {
if (requestCacheControl.getMaxStale() >= 0) {
if (validityStrategy.getFreshnessLifetime(responseCacheControl, entry).toSeconds() > requestCacheControl.getMaxStale()) {
LOG.debug("Response from cache was not suitable due to max stale freshness");
return false;
}
}
if (cacheControl.getMinFresh() >= 0) {
if (cacheControl.getMinFresh() == 0) {
if (requestCacheControl.getMinFresh() >= 0) {
if (requestCacheControl.getMinFresh() == 0) {
return false;
}
final TimeValue age = validityStrategy.getCurrentAge(entry, now);
final TimeValue freshness = validityStrategy.getFreshnessLifetime(entry);
if (freshness.toSeconds() - age.toSeconds() < cacheControl.getMinFresh()) {
final TimeValue freshness = validityStrategy.getFreshnessLifetime(responseCacheControl, entry);
if (freshness.toSeconds() - age.toSeconds() < requestCacheControl.getMinFresh()) {
LOG.debug("Response from cache was not suitable due to min fresh " +
"freshness requirement");
return false;

View File

@ -217,7 +217,8 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
requestCompliance.makeRequestCompliant(request);
request.addHeader(HttpHeaders.VIA, via);
if (!cacheableRequestPolicy.isServableFromCache(request)) {
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (!cacheableRequestPolicy.isServableFromCache(requestCacheControl, request)) {
LOG.debug("Request is not servable from cache");
responseCache.flushCacheEntriesInvalidatedByRequest(target, request);
return callBackend(target, request, scope, chain);
@ -226,9 +227,10 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
if (entry == null) {
LOG.debug("Cache miss");
return handleCacheMiss(target, request, scope, chain);
return handleCacheMiss(target, request, requestCacheControl, scope, chain);
} else {
return handleCacheHit(target, request, scope, chain, entry);
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
return handleCacheHit(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
}
}
@ -276,6 +278,8 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
private ClassicHttpResponse handleCacheHit(
final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl,
final HttpHost target,
final ClassicHttpRequest request,
final ExecChain.Scope scope,
@ -286,41 +290,40 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
recordCacheHit(target, request);
final Instant now = getCurrentDate();
if (requestContainsNoCacheDirective(request)) {
if (requestCacheControl.isNoCache()) {
// Revalidate with the server
return revalidateCacheEntry(target, request, scope, chain, entry);
return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
}
if (suitabilityChecker.canCachedResponseBeUsed(request, entry, now)) {
if (responseCachingPolicy.responseContainsNoCacheDirective(entry)) {
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
// Revalidate with the server due to no-cache directive in response
if (LOG.isDebugEnabled()) {
LOG.debug("Revalidating with server due to no-cache directive in response.");
}
return revalidateCacheEntry(target, request, scope, chain, entry);
return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
}
LOG.debug("Cache hit");
try {
return convert(generateCachedResponse(request, context, entry, now), scope);
return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
} catch (final ResourceIOException ex) {
recordCacheFailure(target, request);
if (!mayCallBackend(request)) {
if (!mayCallBackend(requestCacheControl)) {
return convert(generateGatewayTimeout(context), scope);
}
setResponseStatus(scope.clientContext, CacheResponseStatus.FAILURE);
return chain.proceed(request, scope);
}
} else if (!mayCallBackend(request)) {
} else if (!mayCallBackend(requestCacheControl)) {
LOG.debug("Cache entry not suitable but only-if-cached requested");
return convert(generateGatewayTimeout(context), scope);
} else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
LOG.debug("Revalidating cache entry");
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(entry);
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
try {
if (cacheRevalidator != null
&& !staleResponseNotAllowed(request, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now)
|| staleIfErrorEnabled) {
LOG.debug("Serving stale with asynchronous revalidation");
final String exchangeId = ExecSupport.getNextExchangeId();
@ -331,21 +334,21 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
scope.originalRequest,
scope.execRuntime.fork(null),
HttpClientContext.create());
final SimpleHttpResponse response = generateCachedResponse(request, context, entry, now);
final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, request, context, entry, now);
cacheRevalidator.revalidateCacheEntry(
responseCache.generateKey(target, request, entry),
() -> revalidateCacheEntry(target, request, fork, chain, entry));
() -> revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, fork, chain, entry));
return convert(response, scope);
}
return revalidateCacheEntry(target, request, scope, chain, entry);
return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
} 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(request, context, entry, now), scope);
return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
}
return convert(handleRevalidationFailure(request, context, entry, now), scope);
return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, request, context, entry, now), scope);
}
} else {
LOG.debug("Cache entry not usable; calling backend");
@ -354,6 +357,8 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
ClassicHttpResponse revalidateCacheEntry(
final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl,
final HttpHost target,
final ClassicHttpRequest request,
final ExecChain.Scope scope,
@ -361,7 +366,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final HttpCacheEntry cacheEntry) throws IOException, HttpException {
Instant requestDate = getCurrentDate();
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
scope.originalRequest, cacheEntry);
responseCacheControl, scope.originalRequest, cacheEntry);
ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
try {
@ -394,8 +399,8 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
if (staleIfErrorAppliesTo(statusCode)
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
try {
final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
cachedResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
@ -422,7 +427,8 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
responseCache.flushCacheEntriesInvalidatedByExchange(target, request, backendResponse);
final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(backendResponse);
final boolean cacheable = responseCachingPolicy.isResponseCacheable(responseCacheControl, request, backendResponse);
if (cacheable) {
storeRequestIfModifiedSinceFor304Response(request, backendResponse);
return cacheAndReturnResponse(target, request, backendResponse, scope, requestDate, responseDate);
@ -498,11 +504,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
private ClassicHttpResponse handleCacheMiss(
final HttpHost target,
final ClassicHttpRequest request,
final RequestCacheControl requestCacheControl,
final ExecChain.Scope scope,
final ExecChain chain) throws IOException, HttpException {
recordCacheMiss(target, request);
if (!mayCallBackend(request)) {
if (!mayCallBackend(requestCacheControl)) {
return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
}

View File

@ -28,7 +28,6 @@ package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import java.time.Instant;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -184,6 +183,7 @@ public class CachingExecBase {
}
SimpleHttpResponse generateCachedResponse(
final ResponseCacheControl responseCacheControl,
final HttpRequest request,
final HttpContext context,
final HttpCacheEntry entry,
@ -196,7 +196,7 @@ public class CachingExecBase {
cachedResponse = responseGenerator.generateResponse(request, entry);
}
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
if (TimeValue.isPositive(validityPolicy.getStaleness(entry, now))) {
if (TimeValue.isPositive(validityPolicy.getStaleness(responseCacheControl, entry, now))) {
cachedResponse.addHeader(HttpHeaders.WARNING,"110 localhost \"Response is stale\"");
}
@ -205,7 +205,7 @@ public class CachingExecBase {
final Header header = entry.getFirstHeader(HttpHeaders.DATE);
if (header != null) {
final Instant responseDate = DateUtils.parseStandardDate(header.getValue());
final TimeValue freshnessLifetime = validityPolicy.getFreshnessLifetime(entry);
final TimeValue freshnessLifetime = validityPolicy.getFreshnessLifetime(responseCacheControl, entry);
final TimeValue currentAge = validityPolicy.getCurrentAge(entry, responseDate);
if (freshnessLifetime.compareTo(ONE_DAY) > 0 && currentAge.compareTo(ONE_DAY) > 0) {
cachedResponse.addHeader(HttpHeaders.WARNING,"113 localhost \"Heuristic expiration\"");
@ -220,11 +220,13 @@ public class CachingExecBase {
}
SimpleHttpResponse handleRevalidationFailure(
final RequestCacheControl requestCacheControl,
final ResponseCacheControl responseCacheControl,
final HttpRequest request,
final HttpContext context,
final HttpCacheEntry entry,
final Instant now) throws IOException {
if (staleResponseNotAllowed(request, entry, now)) {
if (staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)) {
return generateGatewayTimeout(context);
} else {
return unvalidatedCacheHit(request, context, entry);
@ -247,14 +249,16 @@ public class CachingExecBase {
return cachedResponse;
}
boolean staleResponseNotAllowed(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
return validityPolicy.mustRevalidate(entry)
|| (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
|| explicitFreshnessRequest(request, entry, now);
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 HttpRequest request) {
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
boolean mayCallBackend(final RequestCacheControl cacheControl) {
if (cacheControl.isOnlyIfCached()) {
LOG.debug("Request marked only-if-cached");
return false;
@ -262,15 +266,16 @@ public class CachingExecBase {
return true;
}
boolean explicitFreshnessRequest(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
if (cacheControl.getMaxStale() >= 0) {
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(entry);
if (age.toSeconds() - lifetime.toSeconds() > cacheControl.getMaxStale()) {
final TimeValue lifetime = validityPolicy.getFreshnessLifetime(responseCacheControl, entry);
if (age.toSeconds() - lifetime.toSeconds() > requestCacheControl.getMaxStale()) {
return true;
}
} else if (cacheControl.getMinFresh() >= 0 || cacheControl.getMaxAge() >= 0) {
} else if (requestCacheControl.getMinFresh() >= 0 || requestCacheControl.getMaxAge() >= 0) {
return true;
}
return false;
@ -375,25 +380,4 @@ public class CachingExecBase {
}
}
/**
* Checks if the provided HttpRequest contains a 'no-cache' directive in its Cache-Control header.
* <p>
* According to the HTTP/1.1 specification, a 'no-cache' directive in a request message means
* that the client allows a stored response to be used only if it is first validated with the
* origin server (or with an intermediate cache that has a fresh response). This directive
* applies to both shared and private caches.
*
* @param request the HttpRequest to check for the 'no-cache' directive
* @return true if the 'no-cache' directive is present in the Cache-Control header of the request,
* false otherwise
*/
boolean requestContainsNoCacheDirective(final HttpRequest request) {
final Iterator<Header> it = request.headerIterator(HttpHeaders.CACHE_CONTROL);
if (it == null || !it.hasNext()) {
return false;
} else {
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parseResponse(it);
return cacheControl.isNoCache();
}
}
}

View File

@ -49,11 +49,12 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
* the entry with the origin. Build the origin {@link org.apache.hc.core5.http.HttpRequest}
* here and return it.
*
* @param cacheControl the cache control directives.
* @param request the original request from the caller
* @param cacheEntry the entry that needs to be re-validated
* @return the wrapped request
*/
public T buildConditionalRequest(final T request, final HttpCacheEntry cacheEntry) {
public T buildConditionalRequest(final ResponseCacheControl cacheControl, final T request, final HttpCacheEntry cacheEntry) {
final T newRequest = messageCopier.create(request);
final Header eTag = cacheEntry.getFirstHeader(HttpHeaders.ETAG);
@ -64,7 +65,6 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
if (lastModified != null) {
newRequest.setHeader(HttpHeaders.IF_MODIFIED_SINCE, lastModified.getValue());
}
final ResponseCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(cacheEntry);
if (cacheControl.isMustRevalidate() || cacheControl.isProxyRevalidate()) {
newRequest.addHeader(HttpHeaders.CACHE_CONTROL, HeaderConstants.CACHE_CONTROL_MAX_AGE + "=0");
}

View File

@ -99,6 +99,8 @@ final class ResponseCacheControl implements CacheControl {
*/
private final Set<String> noCacheFields;
private final boolean undefined;
/**
* Creates a new instance of {@code CacheControl} with the specified values.
*
@ -129,6 +131,16 @@ final class ResponseCacheControl implements CacheControl {
this.staleWhileRevalidate = staleWhileRevalidate;
this.staleIfError = staleIfError;
this.noCacheFields = noCacheFields != null ? Collections.unmodifiableSet(noCacheFields) : Collections.emptySet();
this.undefined = maxAge == -1 &&
sharedMaxAge == -1 &&
!noCache &&
!noStore &&
!cachePrivate &&
!mustRevalidate &&
!proxyRevalidate &&
!cachePublic &&
staleWhileRevalidate == -1
&& staleIfError == -1;
}
/**
@ -235,6 +247,10 @@ final class ResponseCacheControl implements CacheControl {
return noCacheFields;
}
public boolean isUndefined() {
return undefined;
}
@Override
public String toString() {
return "CacheControl{" +
@ -252,7 +268,7 @@ final class ResponseCacheControl implements CacheControl {
'}';
}
static Builder create() {
static Builder builder() {
return new Builder();
}

View File

@ -45,10 +45,8 @@ import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -161,11 +159,9 @@ class ResponseCachingPolicy {
/**
* Determines if an HttpResponse can be cached.
*
* @param httpMethod What type of request was this, a GET, PUT, other?
* @param response The origin response
* @return {@code true} if response is cacheable
*/
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response, final ResponseCacheControl cacheControl) {
public boolean isResponseCacheable(final ResponseCacheControl cacheControl, final String httpMethod, final HttpResponse response) {
boolean cacheable = false;
if (!HeaderConstants.GET_METHOD.equals(httpMethod) && !HeaderConstants.HEAD_METHOD.equals(httpMethod)
@ -242,7 +238,7 @@ class ResponseCachingPolicy {
}
// calculate freshness lifetime
final Duration freshnessLifetime = calculateFreshnessLifetime(response, cacheControl);
final Duration freshnessLifetime = calculateFreshnessLifetime(cacheControl, response);
if (freshnessLifetime.isNegative() || freshnessLifetime.isZero()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Freshness lifetime is invalid");
@ -250,7 +246,7 @@ class ResponseCachingPolicy {
return false;
}
return cacheable || isExplicitlyCacheable(response, cacheControl);
return cacheable || isExplicitlyCacheable(cacheControl, response);
}
private boolean unknownStatusCode(final int status) {
@ -295,57 +291,21 @@ class ResponseCachingPolicy {
}
}
protected boolean isExplicitlyCacheable(final HttpResponse response, final ResponseCacheControl cacheControl ) {
protected boolean isExplicitlyCacheable(final ResponseCacheControl cacheControl, final HttpResponse response) {
if (response.getFirstHeader(HttpHeaders.EXPIRES) != null) {
return true;
}
if (cacheControl == null) {
return false;
}else {
return cacheControl.getMaxAge() > 0 || cacheControl.getSharedMaxAge()>0 ||
cacheControl.isMustRevalidate() || cacheControl.isProxyRevalidate() || (cacheControl.isPublic());
}
return cacheControl.getMaxAge() > 0 || cacheControl.getSharedMaxAge()>0 ||
cacheControl.isMustRevalidate() || cacheControl.isProxyRevalidate() || (cacheControl.isPublic());
}
/**
* Determine if the {@link HttpResponse} gotten from the origin is a
* cacheable response.
*
* @param request the {@link HttpRequest} that generated an origin hit. Can't be {@code null}.
* @param response the {@link HttpResponse} from the origin. Can't be {@code null}.
* @return {@code true} if response is cacheable
* @since 5.3
*/
public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response) {
Args.notNull(request, "Request");
Args.notNull(response, "Response");
return isResponseCacheable(request, response, parseCacheControlHeader(response));
}
/**
* Determines if an HttpResponse can be cached.
*
* @param httpMethod What type of request was this, a GET, PUT, other?. Can't be {@code null}.
* @param response The origin response. Can't be {@code null}.
* @return {@code true} if response is cacheable
* @since 5.3
*/
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) {
Args.notEmpty(httpMethod, "httpMethod");
Args.notNull(response, "Response");
return isResponseCacheable(httpMethod, response, parseCacheControlHeader(response));
}
/**
* Determine if the {@link HttpResponse} gotten from the origin is a
* cacheable response.
*
* @param request the {@link HttpRequest} that generated an origin hit
* @param response the {@link HttpResponse} from the origin
* @return {@code true} if response is cacheable
*/
public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response, final ResponseCacheControl cacheControl) {
public boolean isResponseCacheable(final ResponseCacheControl cacheControl, final HttpRequest request, final HttpResponse response) {
final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : HttpVersion.DEFAULT;
if (version.compareToVersion(HttpVersion.HTTP_1_1) > 0) {
if (LOG.isDebugEnabled()) {
@ -353,7 +313,7 @@ class ResponseCachingPolicy {
}
return false;
}
if (cacheControl != null && cacheControl.isNoStore()) {
if (cacheControl.isNoStore()) {
LOG.debug("Response is explicitly non-cacheable per cache control directive");
return false;
}
@ -362,31 +322,31 @@ class ResponseCachingPolicy {
if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) {
LOG.debug("Response is not cacheable as it had a query string");
return false;
} else if (!neverCache1_1ResponsesWithQueryString && !isExplicitlyCacheable(response, cacheControl)) {
} else if (!neverCache1_1ResponsesWithQueryString && !isExplicitlyCacheable(cacheControl, response)) {
LOG.debug("Response is not cacheable as it is missing explicit caching headers");
return false;
}
}
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response, cacheControl)) {
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(cacheControl, response)) {
LOG.debug("Expires header less or equal to Date header and no cache control directives");
return false;
}
if (sharedCache) {
if (request.countHeaders(HttpHeaders.AUTHORIZATION) > 0
&& cacheControl != null && !(cacheControl.getSharedMaxAge() > -1 || cacheControl.isMustRevalidate() || cacheControl.isPublic())) {
&& !(cacheControl.getSharedMaxAge() > -1 || cacheControl.isMustRevalidate() || cacheControl.isPublic())) {
LOG.debug("Request contains private credentials");
return false;
}
}
final String method = request.getMethod();
return isResponseCacheable(method, response, cacheControl);
return isResponseCacheable(cacheControl, method, response);
}
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final HttpResponse response, final ResponseCacheControl cacheControl) {
if (cacheControl != null) {
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final ResponseCacheControl cacheControl, final HttpResponse response) {
if (!cacheControl.isUndefined()) {
return false;
}
final Header expiresHdr = response.getFirstHeader(HttpHeaders.EXPIRES);
@ -446,9 +406,9 @@ class ResponseCachingPolicy {
* @param response the HTTP response for which to calculate the freshness lifetime
* @return the freshness lifetime of the response, in seconds
*/
private Duration calculateFreshnessLifetime(final HttpResponse response, final ResponseCacheControl cacheControl) {
private Duration calculateFreshnessLifetime(final ResponseCacheControl cacheControl, final HttpResponse response) {
if (cacheControl == null) {
if (cacheControl.isUndefined()) {
// If no cache-control header is present, assume no caching directives and return a default value
return DEFAULT_FRESHNESS_DURATION; // 5 minutes
}
@ -488,43 +448,21 @@ class ResponseCachingPolicy {
* {@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}.
*
* @param entry the cached HTTP message entry to check
* @return {@code true} if a stale response can be served in case of an error status code, {@code false} otherwise
*/
boolean isStaleIfErrorEnabled(final HttpCacheEntry entry) {
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
final ResponseCacheControl cacheControl = parseCacheControlHeader(entry);
if (cacheControl == null) {
return false;
} else {
return cacheControl.getStaleWhileRevalidate() > 0;
}
return cacheControl.getStaleWhileRevalidate() > 0;
}
}
return false;
}
/**
* Parses the Cache-Control header from the given HTTP messageHeaders and returns the corresponding CacheControl instance.
* If the header is not present, returns a CacheControl instance with default values for all directives.
*
* @param messageHeaders the HTTP message to parse the header from
* @return a CacheControl instance with the parsed directives or default values if the header is not present
*/
private ResponseCacheControl parseCacheControlHeader(final MessageHeaders messageHeaders) {
final Iterator<Header> it = messageHeaders.headerIterator(HttpHeaders.CACHE_CONTROL);
if (it == null || !it.hasNext()) {
return null;
} else {
return CacheControlHeaderParser.INSTANCE.parseResponse(it);
}
}
/**
* Determines if the given {@link HttpCacheEntry} requires revalidation based on the presence of the {@code no-cache} directive
* in the Cache-Control header.
@ -539,33 +477,26 @@ class ResponseCachingPolicy {
* @param entry the {@link HttpCacheEntry} containing the headers to check for the {@code no-cache} directive.
* @return true if revalidation is required based on the {@code no-cache} directive, {@code false} otherwise.
*/
boolean responseContainsNoCacheDirective(final HttpCacheEntry entry) {
final ResponseCacheControl responseCacheControl = parseCacheControlHeader(entry);
boolean responseContainsNoCacheDirective(final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry) {
final Set<String> noCacheFields = responseCacheControl.getNoCacheFields();
if (responseCacheControl != null) {
final Set<String> noCacheFields = responseCacheControl.getNoCacheFields();
// If no-cache directive is present and has no field names
if (responseCacheControl.isNoCache() && noCacheFields.isEmpty()) {
LOG.debug("No-cache directive present without field names. Revalidation required.");
return true;
}
// If no-cache directive is present and has no field names
if (responseCacheControl.isNoCache() && noCacheFields.isEmpty()) {
LOG.debug("No-cache directive present without field names. Revalidation required.");
return true;
}
// If no-cache directive is present with field names
if (responseCacheControl.isNoCache()) {
for (final String field : noCacheFields) {
if (entry.getFirstHeader(field) != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("No-cache directive field '{}' found in response headers. Revalidation required.", field);
}
return true;
// If no-cache directive is present with field names
if (responseCacheControl.isNoCache()) {
for (final String field : noCacheFields) {
if (entry.getFirstHeader(field) != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("No-cache directive field '{}' found in response headers. Revalidation required.", field);
}
return true;
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("No no-cache directives found in response headers. No revalidation required.");
}
return false;
}

View File

@ -38,9 +38,7 @@ import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.util.TimeValue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -161,68 +159,75 @@ public class TestCacheValidityPolicy {
@Test
public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.setSharedMaxAge(10)
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
}
@Test
public void testFreshnessLifetimeIsMaxAgeIfPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.setMaxAge(10)
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
}
@Test
public void testFreshnessLifetimeIsMostRestrictiveOfMaxAgeAndSMaxAge() {
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
new BasicHeader("Cache-Control", "s-maxage=20") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.setMaxAge(10)
.setSharedMaxAge(20)
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
new BasicHeader("Cache-Control", "s-maxage=10") };
entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
final ResponseCacheControl cacheControl2 = ResponseCacheControl.builder()
.setMaxAge(20)
.setSharedMaxAge(10)
.build();
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl2, entry));
}
@Test
public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.setMaxAge(10)
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)) };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
}
@Test
public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"),
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.setSharedMaxAge(10)
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)) };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(entry));
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
assertEquals(TimeValue.ofSeconds(10), impl.getFreshnessLifetime(cacheControl, entry));
}
@Test
public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
final Header[] headers = new Header[] {
final ResponseCacheControl cacheControl = ResponseCacheControl.builder()
.build();
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)) };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertEquals(TimeValue.ofSeconds(4), impl.getFreshnessLifetime(entry));
new BasicHeader("Expires", DateUtils.formatStandardDate(sixSecondsAgo)));
assertEquals(TimeValue.ofSeconds(4), impl.getFreshnessLifetime(cacheControl, entry));
}
@Test
public void testHeuristicFreshnessLifetime() {
final Header[] headers = new Header[] {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(oneSecondAgo)),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(elevenSecondsAgo))
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(elevenSecondsAgo)));
assertEquals(TimeValue.ofSeconds(1), impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ZERO_MILLISECONDS));
}
@ -235,18 +240,16 @@ public class TestCacheValidityPolicy {
@Test
public void testHeuristicFreshnessLifetimeIsNonNegative() {
final Header[] headers = new Header[] {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(elevenSecondsAgo)),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneSecondAgo))
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneSecondAgo)));
assertTrue(TimeValue.isNonNegative(impl.getHeuristicFreshnessLifetime(entry, 0.1f, TimeValue.ofSeconds(10))));
}
@Test
public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder().build();
impl = new CacheValidityPolicy() {
@Override
public TimeValue getCurrentAge(final HttpCacheEntry e, final Instant d) {
@ -255,17 +258,18 @@ public class TestCacheValidityPolicy {
return TimeValue.ofSeconds(6);
}
@Override
public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
public TimeValue getFreshnessLifetime(final ResponseCacheControl cacheControl, final HttpCacheEntry e) {
assertSame(entry, e);
return TimeValue.ofSeconds(10);
}
};
assertTrue(impl.isResponseFresh(entry, now));
assertTrue(impl.isResponseFresh(responseCacheControl, entry, now));
}
@Test
public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder().build();
impl = new CacheValidityPolicy() {
@Override
public TimeValue getCurrentAge(final HttpCacheEntry e, final Instant d) {
@ -274,17 +278,18 @@ public class TestCacheValidityPolicy {
return TimeValue.ofSeconds(6);
}
@Override
public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
public TimeValue getFreshnessLifetime(final ResponseCacheControl cacheControl, final HttpCacheEntry e) {
assertSame(entry, e);
return TimeValue.ofSeconds(6);
}
};
assertFalse(impl.isResponseFresh(entry, now));
assertFalse(impl.isResponseFresh(responseCacheControl, entry, now));
}
@Test
public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder().build();
impl = new CacheValidityPolicy() {
@Override
public TimeValue getCurrentAge(final HttpCacheEntry e, final Instant d) {
@ -293,70 +298,70 @@ public class TestCacheValidityPolicy {
return TimeValue.ofSeconds(10);
}
@Override
public TimeValue getFreshnessLifetime(final HttpCacheEntry e) {
public TimeValue getFreshnessLifetime(final ResponseCacheControl cacheControl, final HttpCacheEntry e) {
assertSame(entry, e);
return TimeValue.ofSeconds(6);
}
};
assertFalse(impl.isResponseFresh(entry, now));
assertFalse(impl.isResponseFresh(responseCacheControl, entry, now));
}
@Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
final Header[] headers = {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())),
new BasicHeader("ETag", "somevalue")};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
new BasicHeader("ETag", "somevalue"));
assertTrue(impl.isRevalidatable(entry));
}
@Test
public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
final Header[] headers = {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())) };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())));
assertTrue(impl.isRevalidatable(entry));
}
@Test
public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
final Header[] headers = {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Expires", DateUtils.formatStandardDate(Instant.now())),
new BasicHeader("Cache-Control", "public") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
new BasicHeader("Cache-Control", "public"));
assertFalse(impl.isRevalidatable(entry));
}
@Test
public void testMissingContentLengthDoesntInvalidateEntry() {
final int contentLength = 128;
final Header[] headers = {}; // no Content-Length header
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new Header[] { }, // no Content-Length header
HttpTestUtils.getRandomBytes(contentLength));
assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
}
@Test
public void testCorrectContentLengthDoesntInvalidateEntry() {
final int contentLength = 128;
final Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength)) };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new Header[] { new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength)) },
HttpTestUtils.getRandomBytes(contentLength));
assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
}
@Test
public void testWrongContentLengthInvalidatesEntry() {
final int contentLength = 128;
final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength+1))};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new Header[]{ new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength+1)) },
HttpTestUtils.getRandomBytes(contentLength));
assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
}
@Test
public void testNullResourceInvalidatesEntry() {
final int contentLength = 128;
final Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength))};
final HttpCacheEntry entry = HttpTestUtils.makeHeadCacheEntry(headers);
final HttpCacheEntry entry = HttpTestUtils.makeHeadCacheEntry(
new BasicHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(contentLength)));
assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
}
@ -370,133 +375,108 @@ public class TestCacheValidityPolicy {
@Test
public void testMalformedAgeHeaderValueReturnsMaxAge() {
final Header[] headers = new Header[] { new BasicHeader("Age", "asdf") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Age", "asdf"));
// in seconds
assertEquals(CacheValidityPolicy.MAX_AGE.toSeconds(), impl.getAgeValue(entry));
}
@Test
public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
// in seconds
assertEquals(0L, impl.getMaxAge(entry));
}
@Test
public void testMustRevalidateIsFalseIfDirectiveNotPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertFalse(impl.mustRevalidate(entry));
}
@Test
public void testMustRevalidateIsTrueWhenDirectiveIsPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, must-revalidate") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertTrue(impl.mustRevalidate(entry));
}
@Test
public void testProxyRevalidateIsFalseIfDirectiveNotPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertFalse(impl.proxyRevalidate(entry));
}
@Test
public void testProxyRevalidateIsTrueWhenDirectiveIsPresent() {
final Header[] headers = new Header[] { new BasicHeader("Cache-Control","public, proxy-revalidate") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
assertTrue(impl.proxyRevalidate(entry));
}
@Test
public void testMayReturnStaleIfErrorInResponseIsTrueWithinStaleness(){
final Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-if-error=15")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
final HttpRequest req = new BasicHttpRequest("GET","/");
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
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 Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
final HttpRequest req = new BasicHttpRequest("GET","/");
req.setHeader("Cache-Control","stale-if-error=15");
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
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 Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-if-error=1")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
final HttpRequest req = new BasicHttpRequest("GET","/");
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
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 Header[] headers = new Header[] {
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now,
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
final HttpRequest req = new BasicHttpRequest("GET","/");
req.setHeader("Cache-Control","stale-if-error=1");
assertFalse(impl.mayReturnStaleIfError(req, entry, now));
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 Header[] headers = new Header[] { new BasicHeader("Cache-control", "public") };
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
.setCachePublic(true)
.build();
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now));
}
@Test
public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() {
final Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
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(entry, now));
assertTrue(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now));
}
@Test
public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() {
final Instant twentyFiveSecondsAgo = now.minusSeconds(25);
final Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(twentyFiveSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
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(entry, now));
assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now));
}
@Test
public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() {
final Header[] headers = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=")
};
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
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(entry, now));
assertFalse(impl.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now));
}
}

View File

@ -43,100 +43,106 @@ public class TestCacheableRequestPolicy {
@Test
public void testIsGetServableFromCache() {
final BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder().build();
Assertions.assertTrue(policy.isServableFromCache(request));
Assertions.assertTrue(policy.isServableFromCache(cacheControl, request));
}
@Test
public void testIsGetWithCacheControlServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
request.addHeader("Cache-Control", "no-cache");
final BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder()
.setNoCache(true)
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("GET", "someUri");
request.addHeader("Cache-Control", "no-store");
request.addHeader("Cache-Control", "max-age=20");
final RequestCacheControl cacheControl2 = RequestCacheControl.builder()
.setNoStore(true)
.setMaxAge(20)
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
request = new BasicHttpRequest("GET", "someUri");
request.addHeader("Cache-Control", "public");
request.addHeader("Cache-Control", "no-store, max-age=20");
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl2, request));
}
@Test
public void testIsGetWithPragmaServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
final BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
request.addHeader("Pragma", "no-cache");
final RequestCacheControl cacheControl = RequestCacheControl.builder()
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("GET", "someUri");
request.addHeader("Pragma", "value1");
request.addHeader("Pragma", "value2");
final BasicHttpRequest request2 = new BasicHttpRequest("GET", "someUri");
request2.addHeader("Pragma", "value1");
request2.addHeader("Pragma", "value2");
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request2));
}
@Test
public void testIsHeadServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
final BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder().build();
Assertions.assertTrue(policy.isServableFromCache(request));
Assertions.assertTrue(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Cache-Control", "public");
request.addHeader("Cache-Control", "max-age=20");
final RequestCacheControl cacheControl2 = RequestCacheControl.builder()
.setMaxAge(20)
.build();
Assertions.assertTrue(policy.isServableFromCache(request));
Assertions.assertTrue(policy.isServableFromCache(cacheControl2, request));
}
@Test
public void testIsHeadWithCacheControlServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Cache-Control", "no-cache");
final BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder()
.setNoCache(true)
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Cache-Control", "no-store");
request.addHeader("Cache-Control", "max-age=20");
final RequestCacheControl cacheControl2 = RequestCacheControl.builder()
.setNoStore(true)
.setMaxAge(20)
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Cache-Control", "public");
request.addHeader("Cache-Control", "no-store, max-age=20");
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl2, request));
}
@Test
public void testIsHeadWithPragmaServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Pragma", "no-cache");
final BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder()
.setNoCache(true)
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("HEAD", "someUri");
request.addHeader("Pragma", "value1");
request.addHeader("Pragma", "value2");
final BasicHttpRequest request2 = new BasicHttpRequest("HEAD", "someUri");
request2.addHeader("Pragma", "value1");
request2.addHeader("Pragma", "value2");
final RequestCacheControl cacheControl2 = RequestCacheControl.builder()
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl2, request2));
}
@Test
public void testIsArbitraryMethodServableFromCache() {
BasicHttpRequest request = new BasicHttpRequest("TRACE", "someUri");
final BasicHttpRequest request = new BasicHttpRequest("TRACE", "someUri");
final RequestCacheControl cacheControl = RequestCacheControl.builder()
.build();
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request));
request = new BasicHttpRequest("get", "someUri");
final BasicHttpRequest request2 = new BasicHttpRequest("get", "someUri");
Assertions.assertFalse(policy.isServableFromCache(request));
Assertions.assertFalse(policy.isServableFromCache(cacheControl, request2));
}

View File

@ -32,7 +32,6 @@ import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpRequest;
@ -48,9 +47,10 @@ public class TestCachedResponseSuitabilityChecker {
private Instant tenSecondsAgo;
private Instant nineSecondsAgo;
private HttpHost host;
private HttpRequest request;
private HttpCacheEntry entry;
private RequestCacheControl requestCacheControl;
private ResponseCacheControl responseCacheControl;
private CachedResponseSuitabilityChecker impl;
@BeforeEach
@ -60,9 +60,10 @@ public class TestCachedResponseSuitabilityChecker {
tenSecondsAgo = now.minusSeconds(10);
nineSecondsAgo = now.minusSeconds(9);
host = new HttpHost("foo.example.com");
request = new BasicHttpRequest("GET", "/foo");
entry = HttpTestUtils.makeCacheEntry();
requestCacheControl = RequestCacheControl.builder().build();
responseCacheControl = ResponseCacheControl.builder().build();
impl = new CachedResponseSuitabilityChecker(CacheConfig.DEFAULT);
}
@ -75,117 +76,152 @@ public class TestCachedResponseSuitabilityChecker {
public void testNotSuitableIfContentLengthHeaderIsWrong() {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","1")
};
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testSuitableIfCacheEntryIsFresh() {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testNotSuitableIfCacheEntryIsNotFresh() {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(5)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testNotSuitableIfRequestHasNoCache() {
request.addHeader("Cache-Control", "no-cache");
requestCacheControl = RequestCacheControl.builder()
.setNoCache(true)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testNotSuitableIfAgeExceedsRequestMaxAge() {
request.addHeader("Cache-Control", "max-age=10");
requestCacheControl = RequestCacheControl.builder()
.setMaxAge(10)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testSuitableIfFreshAndAgeIsUnderRequestMaxAge() {
request.addHeader("Cache-Control", "max-age=15");
requestCacheControl = RequestCacheControl.builder()
.setMaxAge(15)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testSuitableIfFreshAndFreshnessLifetimeGreaterThanRequestMinFresh() {
request.addHeader("Cache-Control", "min-fresh=10");
requestCacheControl = RequestCacheControl.builder()
.setMinFresh(10)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testNotSuitableIfFreshnessLifetimeLessThanRequestMinFresh() {
request.addHeader("Cache-Control", "min-fresh=10");
requestCacheControl = RequestCacheControl.builder()
.setMinFresh(10)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=15"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(15)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testSuitableEvenIfStaleButPermittedByRequestMaxStale() {
request.addHeader("Cache-Control", "max-stale=10");
requestCacheControl = RequestCacheControl.builder()
.setMaxStale(10)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(5)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
public void testNotSuitableIfStaleButTooStaleForRequestMaxStale() {
request.addHeader("Cache-Control", "max-stale=2");
requestCacheControl = RequestCacheControl.builder()
.setMaxStale(2)
.build();
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=5"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(5)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -206,7 +242,7 @@ public class TestCachedResponseSuitabilityChecker {
.setHeuristicCoefficient(0.1f).build();
impl = new CachedResponseSuitabilityChecker(config);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -224,7 +260,7 @@ public class TestCachedResponseSuitabilityChecker {
.build();
impl = new CachedResponseSuitabilityChecker(config);
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -232,24 +268,28 @@ public class TestCachedResponseSuitabilityChecker {
final HttpRequest headRequest = new BasicHttpRequest("HEAD", "/foo");
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = getEntry(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(headRequest, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, headRequest, entry, now));
}
@Test
public void testNotSuitableIfRequestMethodIsGETAndEntryResourceIsNull() {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = HttpTestUtils.makeHeadCacheEntry(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -257,12 +297,14 @@ public class TestCachedResponseSuitabilityChecker {
impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = HttpTestUtils.makeCacheEntryWithNoRequestMethodOrEntity(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -270,12 +312,14 @@ public class TestCachedResponseSuitabilityChecker {
impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = HttpTestUtils.makeCacheEntryWithNoRequestMethod(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -283,11 +327,13 @@ public class TestCachedResponseSuitabilityChecker {
impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600")
};
entry = HttpTestUtils.make204CacheEntryWithNoRequestMethod(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
@Test
@ -296,12 +342,14 @@ public class TestCachedResponseSuitabilityChecker {
impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().build());
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Content-Length","128")
};
entry = HttpTestUtils.makeHeadCacheEntryWithNoRequestMethod(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
Assertions.assertTrue(impl.canCachedResponseBeUsed(headRequest, entry, now));
Assertions.assertTrue(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, headRequest, entry, now));
}
@Test
@ -309,11 +357,13 @@ public class TestCachedResponseSuitabilityChecker {
// Prepare a cache entry with HEAD method
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("Cache-Control", "max-age=3600"),
new BasicHeader("Hc-Request-Method", HeaderConstants.HEAD_METHOD)
};
entry = getEntry(headers);
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
// Validate that the cache entry is not suitable for the GET request
Assertions.assertFalse(impl.canCachedResponseBeUsed(request, entry, now));
Assertions.assertFalse(impl.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now));
}
}

View File

@ -39,8 +39,8 @@ import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
@ -1498,16 +1498,16 @@ public class TestCachingExecChain {
// Set up the mock response chain
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any())).thenReturn(true);
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any())).thenReturn(true);
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any(), Mockito.any())).thenReturn(true);
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any(), Mockito.any())).thenReturn(true);
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
// Assuming validityPolicy is a Mockito mock
Mockito.when(validityPolicy.getCurrentAge(Mockito.any(), Mockito.any()))
.thenReturn(TimeValue.ofMilliseconds(0));
// Assuming validityPolicy is a Mockito mock
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any()))
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any(), Mockito.any()))
.thenReturn(TimeValue.ofMilliseconds(0));
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
@ -1599,16 +1599,16 @@ public class TestCachingExecChain {
// Set up the mock response chain
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any())).thenReturn(true);
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any())).thenReturn(true);
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any(), Mockito.any())).thenReturn(true);
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any(), Mockito.any())).thenReturn(true);
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
// Assuming validityPolicy is a Mockito mock
Mockito.when(validityPolicy.getCurrentAge(Mockito.any(), Mockito.any()))
.thenReturn(TimeValue.ofDays(2));
// Assuming validityPolicy is a Mockito mock
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any()))
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any(), Mockito.any()))
.thenReturn(TimeValue.ofDays(2));
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);

View File

@ -70,7 +70,8 @@ public class TestConditionalRequestBuilder {
new BasicHeader("Last-Modified", lastModified) };
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
final HttpRequest newRequest = impl.buildConditionalRequest(basicRequest, cacheEntry);
final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
final HttpRequest newRequest = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
Assertions.assertEquals(theMethod, newRequest.getMethod());
Assertions.assertEquals(theUri, newRequest.getRequestUri());
@ -98,7 +99,8 @@ public class TestConditionalRequestBuilder {
};
final HttpRequest basicRequest = new BasicHttpRequest("GET", "/");
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
final HttpRequest result = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
Assertions.assertEquals(lmDate,
result.getFirstHeader("If-Modified-Since").getValue());
Assertions.assertEquals(etag,
@ -121,7 +123,8 @@ public class TestConditionalRequestBuilder {
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
final HttpRequest newRequest = impl.buildConditionalRequest(basicRequest, cacheEntry);
final ResponseCacheControl cacheControl = ResponseCacheControl.builder().build();
final HttpRequest newRequest = impl.buildConditionalRequest(cacheControl, basicRequest, cacheEntry);
Assertions.assertEquals(theMethod, newRequest.getMethod());
Assertions.assertEquals(theUri, newRequest.getRequestUri());
@ -145,14 +148,18 @@ public class TestConditionalRequestBuilder {
final Header[] cacheEntryHeaders = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Cache-Control","max-age=5, must-revalidate") };
new BasicHeader("ETag", "\"etag\"")
};
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(5)
.setMustRevalidate(true)
.build();
final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertEquals(0, cacheControl.getMaxAge());
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertEquals(0, requestCacheControl.getMaxAge());
}
@Test
@ -165,14 +172,18 @@ public class TestConditionalRequestBuilder {
final Header[] cacheEntryHeaders = new Header[] {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Cache-Control","max-age=5, proxy-revalidate") };
new BasicHeader("ETag", "\"etag\"")
};
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, cacheEntryHeaders);
final HttpRequest result = impl.buildConditionalRequest(basicRequest, cacheEntry);
final ResponseCacheControl responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(5)
.setProxyRevalidate(true)
.build();
final HttpRequest result = impl.buildConditionalRequest(responseCacheControl, basicRequest, cacheEntry);
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertEquals(0, cacheControl.getMaxAge());
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertEquals(0, requestCacheControl.getMaxAge());
}
@Test
@ -195,8 +206,8 @@ public class TestConditionalRequestBuilder {
public void testBuildUnconditionalRequestAddsCacheControlNoCache()
throws Exception {
final HttpRequest result = impl.buildUnconditionalRequest(request);
final RequestCacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertTrue(cacheControl.isNoCache());
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(result);
Assertions.assertTrue(requestCacheControl.isNoCache());
}
@Test