HTTPCLIENT-2277: optimization of internal cache operations
This commit is contained in:
parent
e469cbb78d
commit
5fbef8fc7f
|
@ -30,6 +30,8 @@ import java.io.IOException;
|
|||
import java.io.InterruptedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
@ -44,7 +46,6 @@ import org.apache.hc.client5.http.async.methods.SimpleBody;
|
|||
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
|
||||
import org.apache.hc.client5.http.cache.CacheResponseStatus;
|
||||
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.impl.ExecSupport;
|
||||
|
@ -64,6 +65,7 @@ import org.apache.hc.core5.http.HttpHost;
|
|||
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.Method;
|
||||
import org.apache.hc.core5.http.impl.BasicEntityDetails;
|
||||
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
|
||||
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
|
||||
|
@ -256,22 +258,24 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
}));
|
||||
} else {
|
||||
operation.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
|
||||
operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry entry) {
|
||||
final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, entry != null);
|
||||
public void completed(final CacheMatch result) {
|
||||
final CacheHit hit = result != null ? result.hit : null;
|
||||
final CacheHit root = result != null ? result.root : null;
|
||||
final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, hit != null);
|
||||
if (fatalErrorResponse != null) {
|
||||
triggerResponse(fatalErrorResponse, scope, asyncExecCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry == null) {
|
||||
if (hit == null) {
|
||||
LOG.debug("Cache miss");
|
||||
handleCacheMiss(requestCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
handleCacheMiss(requestCacheControl, root, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
} else {
|
||||
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
|
||||
handleCacheHit(requestCacheControl, responseCacheControl, target, request, entityProducer, scope, chain, asyncExecCallback, entry);
|
||||
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
|
||||
handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,22 +502,24 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
storeRequestIfModifiedSinceFor304Response(request, backendResponse);
|
||||
} else {
|
||||
LOG.debug("Backend response is not cacheable");
|
||||
responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
}
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
LOG.warn("Unable to flush invalidated entries from cache", ex);
|
||||
}
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
LOG.warn("Unable to flush invalidated entries from cache", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.get();
|
||||
if (cachingDataConsumer != null) {
|
||||
|
@ -530,20 +536,20 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
operation.setDependency(responseCache.createEntry(
|
||||
operation.setDependency(responseCache.store(
|
||||
target,
|
||||
request,
|
||||
backendResponse,
|
||||
buffer,
|
||||
requestDate,
|
||||
responseDate,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
new FutureCallback<CacheHit>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry newEntry) {
|
||||
public void completed(final CacheHit hit) {
|
||||
LOG.debug("Backend response successfully cached");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, newEntry);
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
|
@ -572,14 +578,15 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final HttpResponse backendResponse = cachingDataConsumer.backendResponse;
|
||||
if (cacheConfig.isFreshnessCheckEnabled()) {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
operation.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
|
||||
operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry existingEntry) {
|
||||
if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
|
||||
public void completed(final CacheMatch result) {
|
||||
final CacheHit hit = result != null ? result.hit : null;
|
||||
if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
|
||||
LOG.debug("Backend already contains fresher cache entry");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry);
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
|
@ -618,13 +625,13 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
private void handleCacheHit(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final CacheHit hit,
|
||||
final HttpHost target,
|
||||
final HttpRequest request,
|
||||
final AsyncEntityProducer entityProducer,
|
||||
final AsyncExecChain.Scope scope,
|
||||
final AsyncExecChain chain,
|
||||
final AsyncExecCallback asyncExecCallback,
|
||||
final HttpCacheEntry entry) {
|
||||
final AsyncExecCallback asyncExecCallback) {
|
||||
final HttpClientContext context = scope.clientContext;
|
||||
recordCacheHit(target, request);
|
||||
final Instant now = getCurrentDate();
|
||||
|
@ -634,21 +641,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Revalidating with server due to no-cache directive in response.");
|
||||
}
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl,
|
||||
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
|
||||
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, hit.entry, now)) {
|
||||
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, hit.entry)) {
|
||||
// Revalidate with the server due to no-cache directive in response
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl,
|
||||
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
return;
|
||||
}
|
||||
LOG.debug("Cache hit");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
recordCacheFailure(target, request);
|
||||
|
@ -668,15 +673,15 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
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))) {
|
||||
} else if (!(hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
|
||||
LOG.debug("Revalidating cache entry");
|
||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
|
||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
|
||||
if (cacheRevalidator != null
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
|
||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now) || staleIfErrorEnabled)) {
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
|
||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
|
||||
LOG.debug("Serving stale with asynchronous revalidation");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, request, context, entry, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
final String exchangeId = ExecSupport.getNextExchangeId();
|
||||
context.setExchangeId(exchangeId);
|
||||
final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
|
||||
|
@ -689,10 +694,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
scope.scheduler,
|
||||
scope.execCount);
|
||||
cacheRevalidator.revalidateCacheEntry(
|
||||
responseCache.generateKey(target, request, entry),
|
||||
hit.getEntryKey(),
|
||||
asyncExecCallback,
|
||||
asyncExecCallback1 -> revalidateCacheEntry(requestCacheControl, responseCacheControl,
|
||||
target, request, entityProducer, fork, chain, asyncExecCallback1, entry));
|
||||
hit, target, request, entityProducer, fork, chain, asyncExecCallback1));
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (staleIfErrorEnabled) {
|
||||
|
@ -700,8 +705,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
|
||||
}
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl,
|
||||
request, context, entry, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex2) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
|
@ -714,8 +718,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
}
|
||||
}
|
||||
} else {
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl,
|
||||
target, request, entityProducer, scope, chain, asyncExecCallback, entry);
|
||||
revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
} else {
|
||||
LOG.debug("Cache entry not usable; calling backend");
|
||||
|
@ -726,18 +729,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
void revalidateCacheEntry(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final CacheHit hit,
|
||||
final HttpHost target,
|
||||
final HttpRequest request,
|
||||
final AsyncEntityProducer entityProducer,
|
||||
final AsyncExecChain.Scope scope,
|
||||
final AsyncExecChain chain,
|
||||
final AsyncExecCallback asyncExecCallback,
|
||||
final HttpCacheEntry cacheEntry) {
|
||||
final AsyncExecCallback asyncExecCallback) {
|
||||
final Instant requestDate = getCurrentDate();
|
||||
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
|
||||
responseCacheControl,
|
||||
BasicRequestBuilder.copy(scope.originalRequest).build(),
|
||||
cacheEntry);
|
||||
hit.entry);
|
||||
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
|
||||
|
||||
final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
|
||||
|
@ -745,24 +748,23 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
void triggerUpdatedCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate) {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
recordCacheUpdate(scope.clientContext);
|
||||
operation.setDependency(responseCache.updateEntry(
|
||||
target,
|
||||
operation.setDependency(responseCache.update(
|
||||
hit,
|
||||
request,
|
||||
cacheEntry,
|
||||
backendResponse,
|
||||
requestDate,
|
||||
responseDate,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
new FutureCallback<CacheHit>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry updatedEntry) {
|
||||
public void completed(final CacheHit updated) {
|
||||
if (suitabilityChecker.isConditional(request)
|
||||
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, Instant.now())) {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updatedEntry);
|
||||
&& suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updated.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} else {
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
|
@ -785,7 +787,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
void triggerResponseStaleCacheEntry() {
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, cacheEntry);
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
cacheResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
|
@ -804,8 +806,8 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
return new AsyncExecCallbackWrapper(asyncExecCallback, () -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate));
|
||||
}
|
||||
if (staleIfErrorAppliesTo(statusCode)
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
|
||||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())
|
||||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
|
||||
return new AsyncExecCallbackWrapper(asyncExecCallback, this::triggerResponseStaleCacheEntry);
|
||||
}
|
||||
return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
|
||||
|
@ -819,7 +821,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final Instant responseDate1 = getCurrentDate();
|
||||
|
||||
final AsyncExecCallback callback1;
|
||||
if (revalidationResponseIsTooOld(backendResponse1, cacheEntry)
|
||||
if (revalidationResponseIsTooOld(backendResponse1, hit.entry)
|
||||
&& (entityProducer == null || entityProducer.isRepeatable())) {
|
||||
|
||||
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||
|
@ -911,6 +913,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
private void handleCacheMiss(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final CacheHit partialMatch,
|
||||
final HttpHost target,
|
||||
final HttpRequest request,
|
||||
final AsyncEntityProducer entityProducer,
|
||||
|
@ -919,15 +922,19 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final AsyncExecCallback asyncExecCallback) {
|
||||
recordCacheMiss(target, request);
|
||||
|
||||
if (mayCallBackend(requestCacheControl)) {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
operation.setDependency(responseCache.getVariantCacheEntriesWithEtags(
|
||||
target,
|
||||
request,
|
||||
new FutureCallback<Map<String, Variant>>() {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
if (!mayCallBackend(requestCacheControl)) {
|
||||
final SimpleHttpResponse cacheResponse = SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
}
|
||||
|
||||
if (partialMatch != null && partialMatch.entry.isVariantRoot()) {
|
||||
operation.setDependency(responseCache.getVariants(
|
||||
partialMatch,
|
||||
new FutureCallback<Collection<CacheHit>>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Map<String, Variant> variants) {
|
||||
public void completed(final Collection<CacheHit> variants) {
|
||||
if (variants != null && !variants.isEmpty() && (entityProducer == null || entityProducer.isRepeatable())) {
|
||||
negotiateResponseFromVariants(target, request, entityProducer, scope, chain, asyncExecCallback, variants);
|
||||
} else {
|
||||
|
@ -947,8 +954,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
}));
|
||||
} else {
|
||||
final SimpleHttpResponse cacheResponse = SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -959,48 +965,52 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final AsyncExecChain.Scope scope,
|
||||
final AsyncExecChain chain,
|
||||
final AsyncExecCallback asyncExecCallback,
|
||||
final Map<String, Variant> variants) {
|
||||
final Collection<CacheHit> variants) {
|
||||
final CancellableDependency operation = scope.cancellableDependency;
|
||||
final Map<String, CacheHit> variantMap = new HashMap<>();
|
||||
for (final CacheHit variant : variants) {
|
||||
final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (header != null) {
|
||||
variantMap.put(header.getValue(), variant);
|
||||
}
|
||||
}
|
||||
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
|
||||
BasicRequestBuilder.copy(request).build(),
|
||||
variants);
|
||||
variantMap.keySet());
|
||||
|
||||
final Instant requestDate = getCurrentDate();
|
||||
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
|
||||
|
||||
final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
|
||||
|
||||
void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final Variant matchingVariant) {
|
||||
void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final CacheHit match) {
|
||||
recordCacheUpdate(scope.clientContext);
|
||||
final HttpCacheEntry variantEntry = matchingVariant.getEntry();
|
||||
operation.setDependency(responseCache.updateVariantEntry(
|
||||
target,
|
||||
conditionalRequest,
|
||||
operation.setDependency(responseCache.update(
|
||||
match,
|
||||
backendResponse,
|
||||
variantEntry,
|
||||
requestDate,
|
||||
responseDate,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
new FutureCallback<CacheHit>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry responseEntry) {
|
||||
if (shouldSendNotModifiedResponse(request, responseEntry)) {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(responseEntry);
|
||||
public void completed(final CacheHit hit) {
|
||||
if (shouldSendNotModifiedResponse(request, hit.entry)) {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(hit.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} else {
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, responseEntry);
|
||||
operation.setDependency(responseCache.reuseVariantEntryFor(
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
operation.setDependency(responseCache.storeReusing(
|
||||
hit,
|
||||
target,
|
||||
request,
|
||||
backendResponse,
|
||||
responseEntry,
|
||||
requestDate,
|
||||
responseDate,
|
||||
new FutureCallback<Boolean>() {
|
||||
new FutureCallback<CacheHit>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
public void completed(final CacheHit result) {
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
}
|
||||
|
||||
|
@ -1044,29 +1054,29 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final AsyncExecCallback callback;
|
||||
// Handle 304 Not Modified responses
|
||||
if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
|
||||
responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
|
||||
responseCache.match(target, request, new FutureCallback<CacheMatch>() {
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry existingEntry) {
|
||||
if (existingEntry != null) {
|
||||
public void completed(final CacheMatch result) {
|
||||
final CacheHit hit = result != null ? result.hit : null;
|
||||
if (hit != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Existing cache entry found, updating cache entry");
|
||||
}
|
||||
responseCache.updateEntry(
|
||||
target,
|
||||
responseCache.update(
|
||||
hit,
|
||||
request,
|
||||
existingEntry,
|
||||
backendResponse,
|
||||
requestDate,
|
||||
responseDate,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
new FutureCallback<CacheHit>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry updatedEntry) {
|
||||
public void completed(final CacheHit updated) {
|
||||
try {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Cache entry updated, generating response from updated entry");
|
||||
}
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updated.entry);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
|
@ -1113,18 +1123,18 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
|
||||
} else {
|
||||
final String resultEtag = resultEtagHeader.getValue();
|
||||
final Variant matchingVariant = variants.get(resultEtag);
|
||||
if (matchingVariant == null) {
|
||||
final CacheHit match = variantMap.get(resultEtag);
|
||||
if (match == null) {
|
||||
LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
|
||||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
|
||||
} else {
|
||||
if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
|
||||
if (revalidationResponseIsTooOld(backendResponse, match.entry)) {
|
||||
final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||
BasicRequestBuilder.copy(request).build());
|
||||
scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
|
||||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback));
|
||||
} else {
|
||||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, matchingVariant));
|
||||
callback = new AsyncExecCallbackWrapper(asyncExecCallback, () -> updateVariantCacheEntry(backendResponse, responseDate, match));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,24 +27,26 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
|
||||
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.impl.Operations;
|
||||
import org.apache.hc.core5.concurrent.Cancellable;
|
||||
import org.apache.hc.core5.concurrent.ComplexCancellable;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
|
@ -90,111 +92,152 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
|
||||
final String root = cacheKeyGenerator.generateKey(host, request);
|
||||
if (cacheEntry != null && cacheEntry.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
|
||||
if (!variantNames.isEmpty()) {
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
return variantKey + root;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesFor(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
public Cancellable match(final HttpHost host, final HttpRequest request, final FutureCallback<CacheMatch> callback) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
|
||||
LOG.debug("Get cache entry: {}", rootKey);
|
||||
}
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByRequest(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
|
||||
}
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
Cancellable storeInCache(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
if (entry.hasVariants()) {
|
||||
return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
|
||||
} else {
|
||||
return storeEntry(rootKey, entry, callback);
|
||||
}
|
||||
}
|
||||
|
||||
Cancellable storeEntry(
|
||||
final String cacheKey,
|
||||
final HttpCacheEntry entry,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
return storage.putEntry(cacheKey, entry, new FutureCallback<Boolean>() {
|
||||
final ComplexCancellable complexCancellable = new ComplexCancellable();
|
||||
complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(result);
|
||||
public void completed(final HttpCacheEntry root) {
|
||||
if (root != null) {
|
||||
if (root.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(root);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String cacheKey = root.getVariantMap().get(variantKey);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get cache variant entry: {}", cacheKey);
|
||||
}
|
||||
if (cacheKey != null) {
|
||||
complexCancellable.setDependency(storage.getEntry(
|
||||
cacheKey,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry entry) {
|
||||
callback.completed(new CacheMatch(
|
||||
entry != null ? new CacheHit(rootKey, cacheKey, entry) : null,
|
||||
new CacheHit(rootKey, root)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
|
||||
}
|
||||
callback.completed(null);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback.completed(new CacheMatch(new CacheHit(rootKey, root), null));
|
||||
} else {
|
||||
callback.completed(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error storing cache entry with key {}", cacheKey);
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
callback.completed(null);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
return complexCancellable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable getVariants(
|
||||
final CacheHit hit, final FutureCallback<Collection<CacheHit>> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get variant cache entries: {}", hit.rootKey);
|
||||
}
|
||||
final ComplexCancellable complexCancellable = new ComplexCancellable();
|
||||
final HttpCacheEntry root = hit.entry;
|
||||
if (root != null && root.isVariantRoot()) {
|
||||
final Set<String> variantCacheKeys = root.getVariantMap().keySet();
|
||||
complexCancellable.setDependency(storage.getEntries(
|
||||
variantCacheKeys,
|
||||
new FutureCallback<Map<String, HttpCacheEntry>>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Map<String, HttpCacheEntry> resultMap) {
|
||||
final List<CacheHit> cacheHits = resultMap.entrySet().stream()
|
||||
.map(e -> new CacheHit(hit.rootKey, e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
callback.completed(cacheHits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with keys {}", variantCacheKeys);
|
||||
}
|
||||
callback.completed(Collections.emptyList());
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
} else {
|
||||
callback.completed(Collections.emptyList());
|
||||
}
|
||||
return complexCancellable;
|
||||
}
|
||||
|
||||
Cancellable storeEntry(
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put entry in cache: {}", rootKey);
|
||||
}
|
||||
|
||||
return storage.putEntry(rootKey, entry, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(new CacheHit(rootKey, entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error storing cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(new CacheHit(rootKey, entry));
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
@ -208,21 +251,30 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
});
|
||||
}
|
||||
|
||||
Cancellable storeVariantEntry(
|
||||
Cancellable storeVariant(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String variantCacheKey = variantKey + rootKey;
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put variant entry in cache: {}", variantCacheKey);
|
||||
}
|
||||
|
||||
return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update root entry: {}", rootKey);
|
||||
}
|
||||
|
||||
storage.updateEntry(rootKey,
|
||||
existing -> {
|
||||
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
|
||||
|
@ -233,7 +285,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(result);
|
||||
callback.completed(new CacheHit(rootKey, variantCacheKey, entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -265,6 +317,165 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error updating cache entry with key {}", variantCacheKey);
|
||||
}
|
||||
callback.completed(new CacheHit(rootKey, variantCacheKey, entry));
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Cancellable store(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
if (entry.hasVariants()) {
|
||||
return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
|
||||
} else {
|
||||
return storeEntry(rootKey, entry, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable store(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final ByteArrayBuffer content,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Create cache entry: {}", rootKey);
|
||||
}
|
||||
final Resource resource;
|
||||
try {
|
||||
resource = content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null;
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error creating cache entry with key {}", rootKey);
|
||||
}
|
||||
final HttpCacheEntry backup = cacheEntryFactory.create(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
request,
|
||||
originResponse,
|
||||
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
|
||||
callback.completed(new CacheHit(rootKey, backup));
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource);
|
||||
return store(
|
||||
request,
|
||||
originResponse,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
rootKey,
|
||||
entry,
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable update(
|
||||
final CacheHit stale,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
final String entryKey = stale.getEntryKey();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry: {}", entryKey);
|
||||
}
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale.entry);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put entry in cache: {}", entryKey);
|
||||
}
|
||||
|
||||
return store(
|
||||
request,
|
||||
originResponse,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
entryKey,
|
||||
updatedEntry,
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable update(
|
||||
final CacheHit stale,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
final String entryKey = stale.getEntryKey();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry (no root): {}", entryKey);
|
||||
}
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale.entry);
|
||||
return storeEntry(
|
||||
entryKey,
|
||||
updatedEntry,
|
||||
callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable storeReusing(
|
||||
final CacheHit hit,
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<CacheHit> callback) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
|
||||
}
|
||||
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesFor(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries: {}", rootKey);
|
||||
}
|
||||
return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
|
@ -280,304 +491,25 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cancellable reuseVariantEntryFor(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
public Cancellable flushCacheEntriesInvalidatedByRequest(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
|
||||
LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable updateEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpCacheEntry stale,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<HttpCacheEntry> callback) {
|
||||
public Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
|
||||
LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale);
|
||||
return storeInCache(
|
||||
request,
|
||||
originResponse,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
rootKey,
|
||||
updatedEntry,
|
||||
new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(updatedEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable updateVariantEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<HttpCacheEntry> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
entry);
|
||||
return storeEntry(rootKey, updatedEntry, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(updatedEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable createEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final ByteArrayBuffer content,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final FutureCallback<HttpCacheEntry> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
try {
|
||||
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
|
||||
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
|
||||
return storeInCache(
|
||||
request,
|
||||
originResponse,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
rootKey,
|
||||
entry, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error creating cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(cacheEntryFactory.create(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
request,
|
||||
originResponse,
|
||||
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null));
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable getCacheEntry(final HttpHost host, final HttpRequest request, final FutureCallback<HttpCacheEntry> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final ComplexCancellable complexCancellable = new ComplexCancellable();
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry root) {
|
||||
if (root != null) {
|
||||
if (root.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(root);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String variantCacheKey = root.getVariantMap().get(variantKey);
|
||||
if (variantCacheKey != null) {
|
||||
complexCancellable.setDependency(storage.getEntry(
|
||||
variantCacheKey,
|
||||
new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry result) {
|
||||
callback.completed(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
|
||||
}
|
||||
callback.completed(null);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.completed(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(null);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
return complexCancellable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable getVariantCacheEntriesWithEtags(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Map<String, Variant>> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final ComplexCancellable complexCancellable = new ComplexCancellable();
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final Map<String, Variant> variants = new HashMap<>();
|
||||
complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry rootEntry) {
|
||||
if (rootEntry != null && rootEntry.isVariantRoot()) {
|
||||
final Set<String> variantCacheKeys = rootEntry.getVariantMap().keySet();
|
||||
complexCancellable.setDependency(storage.getEntries(
|
||||
variantCacheKeys,
|
||||
new FutureCallback<Map<String, HttpCacheEntry>>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Map<String, HttpCacheEntry> resultMap) {
|
||||
for (final Map.Entry<String, HttpCacheEntry> resultMapEntry : resultMap.entrySet()) {
|
||||
final String cacheKey = resultMapEntry.getKey();
|
||||
final HttpCacheEntry cacheEntry = resultMapEntry.getValue();
|
||||
final Header etagHeader = cacheEntry.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (etagHeader != null) {
|
||||
variants.put(etagHeader.getValue(), new Variant(cacheKey, cacheEntry));
|
||||
}
|
||||
}
|
||||
callback.completed(variants);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with keys {}", variantCacheKeys);
|
||||
}
|
||||
callback.completed(variants);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
} else {
|
||||
callback.completed(variants);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
|
||||
}
|
||||
callback.completed(variants);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
}));
|
||||
return complexCancellable;
|
||||
callback.completed(Boolean.TRUE);
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,19 +27,21 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
|
@ -92,32 +94,236 @@ class BasicHttpCache implements HttpCache {
|
|||
this(CacheConfig.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
|
||||
final String root = cacheKeyGenerator.generateKey(host, request);
|
||||
if (cacheEntry != null && cacheEntry.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
|
||||
if (!variantNames.isEmpty()) {
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
return variantKey + root;
|
||||
void storeInternal(final String cacheKey, final HttpCacheEntry entry) {
|
||||
try {
|
||||
storage.putEntry(cacheKey, entry);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error storing cache entry with key {}", cacheKey);
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
void updateInternal(final String cacheKey, final HttpCacheCASOperation casOperation) {
|
||||
try {
|
||||
storage.updateEntry(cacheKey, casOperation);
|
||||
} catch (final HttpCacheUpdateException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Cannot update cache entry with key {}", cacheKey);
|
||||
}
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpCacheEntry getInternal(final String cacheKey) {
|
||||
try {
|
||||
return storage.getEntry(cacheKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMatch match(final HttpHost host, final HttpRequest request) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get cache root entry: {}", rootKey);
|
||||
}
|
||||
final HttpCacheEntry root = getInternal(rootKey);
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
if (root.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(root);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String cacheKey = root.getVariantMap().get(variantKey);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get cache variant entry: {}", cacheKey);
|
||||
}
|
||||
if (cacheKey != null) {
|
||||
final HttpCacheEntry entry = getInternal(cacheKey);
|
||||
if (entry != null) {
|
||||
return new CacheMatch(new CacheHit(rootKey, cacheKey, entry), new CacheHit(rootKey, root));
|
||||
}
|
||||
}
|
||||
return new CacheMatch(null, new CacheHit(rootKey, root));
|
||||
} else {
|
||||
return new CacheMatch(new CacheHit(rootKey, root), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CacheHit> getVariants(final CacheHit hit) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get variant cache entries: {}", hit.rootKey);
|
||||
}
|
||||
final HttpCacheEntry root = hit.entry;
|
||||
if (root != null && root.isVariantRoot()) {
|
||||
final List<CacheHit> variants = new ArrayList<>();
|
||||
for (final String variantKey : root.getVariantMap().values()) {
|
||||
final HttpCacheEntry variant = getInternal(variantKey);
|
||||
if (variant != null) {
|
||||
variants.add(new CacheHit(hit.rootKey, variantKey, variant));
|
||||
}
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
CacheHit store(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry) {
|
||||
if (entry.hasVariants()) {
|
||||
return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry);
|
||||
} else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put entry in cache: {}", rootKey);
|
||||
}
|
||||
storeInternal(rootKey, entry);
|
||||
return new CacheHit(rootKey, entry);
|
||||
}
|
||||
}
|
||||
|
||||
CacheHit storeVariant(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String variantCacheKey = variantKey + rootKey;
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put variant entry in cache: {}", variantCacheKey);
|
||||
}
|
||||
|
||||
storeInternal(variantCacheKey, entry);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update root entry: {}", rootKey);
|
||||
}
|
||||
|
||||
updateInternal(rootKey, existing -> {
|
||||
final Map<String, String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
|
||||
variantMap.put(variantKey, variantCacheKey);
|
||||
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
|
||||
});
|
||||
return new CacheHit(rootKey, variantCacheKey, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheHit store(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final ByteArrayBuffer content,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Store cache entry: {}", rootKey);
|
||||
}
|
||||
final Resource resource;
|
||||
try {
|
||||
resource = content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null;
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error creating cache entry with key {}", rootKey);
|
||||
}
|
||||
final HttpCacheEntry backup = cacheEntryFactory.create(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
request,
|
||||
originResponse,
|
||||
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
|
||||
return new CacheHit(rootKey, backup);
|
||||
}
|
||||
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource);
|
||||
return store(request, originResponse, requestSent, responseReceived, rootKey, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheHit update(
|
||||
final CacheHit stale,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
final String entryKey = stale.getEntryKey();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry: {}", entryKey);
|
||||
}
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale.entry);
|
||||
return store(request, originResponse, requestSent, responseReceived, entryKey, updatedEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheHit update(
|
||||
final CacheHit stale,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
final String entryKey = stale.getEntryKey();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry (no root)): {}", entryKey);
|
||||
}
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale.entry);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Put entry in cache: {}", entryKey);
|
||||
}
|
||||
|
||||
storeInternal(entryKey, updatedEntry);
|
||||
return new CacheHit(stale.rootKey, stale.variantKey, updatedEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheHit storeReusing(
|
||||
final CacheHit hit,
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
|
||||
}
|
||||
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
|
||||
LOG.debug("Flush cache entries: {}", rootKey);
|
||||
}
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
try {
|
||||
storage.removeEntry(rootKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
}
|
||||
try {
|
||||
storage.removeEntry(rootKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,218 +346,4 @@ class BasicHttpCache implements HttpCache {
|
|||
}
|
||||
}
|
||||
|
||||
void storeInCache(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry) {
|
||||
if (entry.hasVariants()) {
|
||||
storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
|
||||
} else {
|
||||
storeEntry(rootKey, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void storeEntry(final String cacheKey, final HttpCacheEntry entry) {
|
||||
try {
|
||||
storage.putEntry(cacheKey, entry);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error storing cache entry with key {}", cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void storeVariantEntry(
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived,
|
||||
final String rootKey,
|
||||
final HttpCacheEntry entry) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String variantCacheKey = variantKey + request;
|
||||
storeEntry(variantCacheKey, entry);
|
||||
try {
|
||||
storage.updateEntry(rootKey, existing -> {
|
||||
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
|
||||
variantMap.put(variantKey, variantCacheKey);
|
||||
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
|
||||
});
|
||||
} catch (final HttpCacheUpdateException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Cannot update cache entry with key {}", rootKey);
|
||||
}
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error updating cache entry with key {}", rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reuseVariantEntryFor(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry updateEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpCacheEntry stale,
|
||||
final HttpResponse originResponse,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
stale);
|
||||
storeInCache(request, originResponse, requestSent, responseReceived, rootKey, updatedEntry);
|
||||
return updatedEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry updateVariantEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse,
|
||||
entry);
|
||||
storeEntry(rootKey, updatedEntry);
|
||||
return updatedEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry createEntry(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse originResponse,
|
||||
final ByteArrayBuffer content,
|
||||
final Instant requestSent,
|
||||
final Instant responseReceived) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
try {
|
||||
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
|
||||
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
|
||||
storeInCache(request, originResponse, requestSent, responseReceived, rootKey, entry);
|
||||
return entry;
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error creating cache entry with key {}", rootKey);
|
||||
}
|
||||
return cacheEntryFactory.create(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
request,
|
||||
originResponse,
|
||||
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry root;
|
||||
try {
|
||||
root = storage.getEntry(rootKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
if (root.isVariantRoot()) {
|
||||
final List<String> variantNames = CacheKeyGenerator.variantNames(root);
|
||||
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
|
||||
final String variantCacheKey = root.getVariantMap().get(variantKey);
|
||||
if (variantCacheKey != null) {
|
||||
try {
|
||||
return storage.getEntry(variantCacheKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
final Map<String,Variant> variants = new HashMap<>();
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
final HttpCacheEntry root;
|
||||
try {
|
||||
root = storage.getEntry(rootKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
if (root != null && root.isVariantRoot()) {
|
||||
for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
|
||||
final String variantCacheKey = variant.getValue();
|
||||
try {
|
||||
final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
|
||||
if (entry != null) {
|
||||
final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (etagHeader != null) {
|
||||
variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
|
||||
}
|
||||
}
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error retrieving cache entry with key {}", variantCacheKey);
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
}
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
60
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheHit.java
vendored
Normal file
60
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheHit.java
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
|
||||
class CacheHit {
|
||||
|
||||
final String rootKey;
|
||||
final String variantKey;
|
||||
final HttpCacheEntry entry;
|
||||
|
||||
public CacheHit(final String rootKey, final String variantKey, final HttpCacheEntry entry) {
|
||||
this.rootKey = rootKey;
|
||||
this.variantKey = variantKey;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public CacheHit(final String rootKey, final HttpCacheEntry entry) {
|
||||
this(rootKey, null, entry);
|
||||
}
|
||||
|
||||
public String getEntryKey() {
|
||||
return variantKey != null ? variantKey : rootKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheHit{" +
|
||||
"rootKey='" + rootKey + '\'' +
|
||||
", variantKey='" + variantKey + '\'' +
|
||||
", entry=" + entry +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -26,30 +26,26 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
/**
|
||||
* Represents a full match or a partial match (variant options)
|
||||
* result of a cache lookup operation.
|
||||
*/
|
||||
class CacheMatch {
|
||||
|
||||
/** Records a set of information describing a cached variant. */
|
||||
class Variant {
|
||||
final CacheHit hit;
|
||||
final CacheHit root;
|
||||
|
||||
private final String cacheKey;
|
||||
private final HttpCacheEntry entry;
|
||||
|
||||
public Variant(final String cacheKey, final HttpCacheEntry entry) {
|
||||
this.cacheKey = cacheKey;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
public HttpCacheEntry getEntry() {
|
||||
return entry;
|
||||
CacheMatch(final CacheHit hit, final CacheHit root) {
|
||||
this.hit = hit;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cacheKey;
|
||||
return "CacheLookupResult{" +
|
||||
"hit=" + hit +
|
||||
", root=" + root +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -64,7 +64,7 @@ class CachedHttpResponseGenerator {
|
|||
* @return {@link SimpleHttpResponse} constructed response
|
||||
*/
|
||||
SimpleHttpResponse generateResponse(final HttpRequest request, final HttpCacheEntry entry) throws ResourceIOException {
|
||||
final Instant now =Instant.now();
|
||||
final Instant now = Instant.now();
|
||||
final SimpleHttpResponse response = new SimpleHttpResponse(entry.getStatus());
|
||||
response.setVersion(HttpVersion.DEFAULT);
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
|
@ -37,7 +39,6 @@ import org.apache.hc.client5.http.HttpRoute;
|
|||
import org.apache.hc.client5.http.async.methods.SimpleBody;
|
||||
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
|
||||
import org.apache.hc.client5.http.cache.CacheResponseStatus;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
|
@ -56,6 +57,7 @@ import org.apache.hc.core5.http.HttpHost;
|
|||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.Method;
|
||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
|
@ -165,9 +167,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
|
||||
return new BasicClassicHttpResponse(HttpStatus.SC_NOT_IMPLEMENTED);
|
||||
}
|
||||
final HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
|
||||
final CacheMatch result = responseCache.match(target, request);
|
||||
final CacheHit hit = result != null ? result.hit : null;
|
||||
final CacheHit root = result != null ? result.root : null;
|
||||
|
||||
final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, entry != null);
|
||||
final SimpleHttpResponse fatalErrorResponse = getFatallyNonCompliantResponse(request, context, hit != null);
|
||||
if (fatalErrorResponse != null) {
|
||||
return convert(fatalErrorResponse, scope);
|
||||
}
|
||||
|
@ -183,12 +187,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
}
|
||||
|
||||
|
||||
if (entry == null) {
|
||||
if (hit == null) {
|
||||
LOG.debug("Cache miss");
|
||||
return handleCacheMiss(target, request, requestCacheControl, scope, chain);
|
||||
return handleCacheMiss(requestCacheControl, root, target, request, scope, chain);
|
||||
} else {
|
||||
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(entry);
|
||||
return handleCacheHit(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
|
||||
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
|
||||
return handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,11 +242,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
private ClassicHttpResponse handleCacheHit(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final CacheHit hit,
|
||||
final HttpHost target,
|
||||
final ClassicHttpRequest request,
|
||||
final ExecChain.Scope scope,
|
||||
final ExecChain chain,
|
||||
final HttpCacheEntry entry) throws IOException, HttpException {
|
||||
final ExecChain chain) throws IOException, HttpException {
|
||||
final HttpClientContext context = scope.clientContext;
|
||||
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
|
||||
recordCacheHit(target, request);
|
||||
|
@ -250,20 +254,20 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
|
||||
if (requestCacheControl.isNoCache()) {
|
||||
// Revalidate with the server
|
||||
return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
|
||||
return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
|
||||
}
|
||||
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, entry, now)) {
|
||||
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, entry)) {
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(requestCacheControl, responseCacheControl, request, hit.entry, now)) {
|
||||
if (responseCachingPolicy.responseContainsNoCacheDirective(responseCacheControl, hit.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(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
|
||||
return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
|
||||
}
|
||||
LOG.debug("Cache hit");
|
||||
try {
|
||||
return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
|
||||
} catch (final ResourceIOException ex) {
|
||||
recordCacheFailure(target, request);
|
||||
if (!mayCallBackend(requestCacheControl)) {
|
||||
|
@ -275,13 +279,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
} 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))) {
|
||||
} else if (!(hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
|
||||
LOG.debug("Revalidating cache entry");
|
||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, entry);
|
||||
final boolean staleIfErrorEnabled = responseCachingPolicy.isStaleIfErrorEnabled(responseCacheControl, hit.entry);
|
||||
try {
|
||||
if (cacheRevalidator != null
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)
|
||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, entry, now) || staleIfErrorEnabled)) {
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, now)
|
||||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
|
||||
LOG.debug("Serving stale with asynchronous revalidation");
|
||||
final String exchangeId = ExecSupport.getNextExchangeId();
|
||||
context.setExchangeId(exchangeId);
|
||||
|
@ -291,21 +295,21 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
scope.originalRequest,
|
||||
scope.execRuntime.fork(null),
|
||||
HttpClientContext.create());
|
||||
final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, request, context, entry, now);
|
||||
final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
cacheRevalidator.revalidateCacheEntry(
|
||||
responseCache.generateKey(target, request, entry),
|
||||
() -> revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, fork, chain, entry));
|
||||
hit.getEntryKey(),
|
||||
() -> revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, fork, chain));
|
||||
return convert(response, scope);
|
||||
}
|
||||
return revalidateCacheEntry(requestCacheControl, responseCacheControl, target, request, scope, chain, entry);
|
||||
return revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, scope, chain);
|
||||
} catch (final IOException ioex) {
|
||||
if (staleIfErrorEnabled) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
|
||||
}
|
||||
return convert(generateCachedResponse(responseCacheControl, request, context, entry, now), scope);
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
|
||||
}
|
||||
return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, request, context, entry, now), scope);
|
||||
return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, hit.entry, request, context, now), scope);
|
||||
}
|
||||
} else {
|
||||
LOG.debug("Cache entry not usable; calling backend");
|
||||
|
@ -316,20 +320,20 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
ClassicHttpResponse revalidateCacheEntry(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final CacheHit hit,
|
||||
final HttpHost target,
|
||||
final ClassicHttpRequest request,
|
||||
final ExecChain.Scope scope,
|
||||
final ExecChain chain,
|
||||
final HttpCacheEntry cacheEntry) throws IOException, HttpException {
|
||||
final ExecChain chain) throws IOException, HttpException {
|
||||
Instant requestDate = getCurrentDate();
|
||||
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
|
||||
responseCacheControl, scope.originalRequest, cacheEntry);
|
||||
responseCacheControl, scope.originalRequest, hit.entry);
|
||||
|
||||
ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
|
||||
try {
|
||||
Instant responseDate = getCurrentDate();
|
||||
|
||||
if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
|
||||
if (revalidationResponseIsTooOld(backendResponse, hit.entry)) {
|
||||
backendResponse.close();
|
||||
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
|
||||
scope.originalRequest);
|
||||
|
@ -346,20 +350,19 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
}
|
||||
|
||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
||||
final HttpCacheEntry updatedEntry = responseCache.updateEntry(
|
||||
target, request, cacheEntry, backendResponse, requestDate, responseDate);
|
||||
final CacheHit updated = responseCache.update(hit, request, backendResponse, requestDate, responseDate);
|
||||
if (suitabilityChecker.isConditional(request)
|
||||
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, Instant.now())) {
|
||||
return convert(responseGenerator.generateNotModifiedResponse(updatedEntry), scope);
|
||||
&& suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
|
||||
return convert(responseGenerator.generateNotModifiedResponse(updated.entry), scope);
|
||||
}
|
||||
return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
|
||||
return convert(responseGenerator.generateResponse(request, updated.entry), scope);
|
||||
}
|
||||
|
||||
if (staleIfErrorAppliesTo(statusCode)
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, cacheEntry, getCurrentDate())
|
||||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, cacheEntry, responseDate)) {
|
||||
&& !staleResponseNotAllowed(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())
|
||||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
|
||||
try {
|
||||
final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
|
||||
final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
cachedResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
|
||||
return convert(cachedResponse, scope);
|
||||
} finally {
|
||||
|
@ -391,7 +394,9 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
return cacheAndReturnResponse(target, request, backendResponse, scope, requestDate, responseDate);
|
||||
}
|
||||
LOG.debug("Backend response is not cacheable");
|
||||
responseCache.flushCacheEntriesFor(target, request);
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
responseCache.flushCacheEntriesFor(target, request);
|
||||
}
|
||||
return backendResponse;
|
||||
}
|
||||
|
||||
|
@ -406,16 +411,16 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
|
||||
// handle 304 Not Modified responses
|
||||
if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
|
||||
final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
|
||||
if (existingEntry != null) {
|
||||
final HttpCacheEntry updatedEntry = responseCache.updateEntry(
|
||||
target,
|
||||
final CacheMatch result = responseCache.match(target ,request);
|
||||
final CacheHit hit = result != null ? result.hit : null;
|
||||
if (hit != null) {
|
||||
final CacheHit updated = responseCache.update(
|
||||
hit,
|
||||
request,
|
||||
existingEntry,
|
||||
backendResponse,
|
||||
requestSent,
|
||||
responseReceived);
|
||||
return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
|
||||
return convert(responseGenerator.generateResponse(request, updated.entry), scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,27 +446,28 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
}
|
||||
backendResponse.close();
|
||||
|
||||
final HttpCacheEntry cacheEntry;
|
||||
CacheHit hit;
|
||||
if (cacheConfig.isFreshnessCheckEnabled()) {
|
||||
final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
|
||||
if (DateSupport.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
|
||||
final CacheMatch result = responseCache.match(target ,request);
|
||||
hit = result != null ? result.hit : null;
|
||||
if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
|
||||
LOG.debug("Backend already contains fresher cache entry");
|
||||
cacheEntry = existingEntry;
|
||||
} else {
|
||||
cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
|
||||
hit = responseCache.store(target, request, backendResponse, buf, requestSent, responseReceived);
|
||||
LOG.debug("Backend response successfully cached");
|
||||
}
|
||||
} else {
|
||||
cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
|
||||
hit = responseCache.store(target, request, backendResponse, buf, requestSent, responseReceived);
|
||||
LOG.debug("Backend response successfully cached (freshness check skipped)");
|
||||
}
|
||||
return convert(responseGenerator.generateResponse(request, cacheEntry), scope);
|
||||
return convert(responseGenerator.generateResponse(request, hit.entry), scope);
|
||||
}
|
||||
|
||||
private ClassicHttpResponse handleCacheMiss(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final CacheHit partialMatch,
|
||||
final HttpHost target,
|
||||
final ClassicHttpRequest request,
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ExecChain.Scope scope,
|
||||
final ExecChain chain) throws IOException, HttpException {
|
||||
recordCacheMiss(target, request);
|
||||
|
@ -469,10 +475,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
if (!mayCallBackend(requestCacheControl)) {
|
||||
return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
|
||||
}
|
||||
|
||||
final Map<String, Variant> variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
|
||||
if (variants != null && !variants.isEmpty()) {
|
||||
return negotiateResponseFromVariants(target, request, scope, chain, variants);
|
||||
if (partialMatch != null && partialMatch.entry.isVariantRoot()) {
|
||||
final List<CacheHit> variants = responseCache.getVariants(partialMatch);
|
||||
if (variants != null && !variants.isEmpty()) {
|
||||
return negotiateResponseFromVariants(target, request, scope, chain, variants);
|
||||
}
|
||||
}
|
||||
|
||||
return callBackend(target, request, scope, chain);
|
||||
|
@ -483,8 +490,16 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
final ClassicHttpRequest request,
|
||||
final ExecChain.Scope scope,
|
||||
final ExecChain chain,
|
||||
final Map<String, Variant> variants) throws IOException, HttpException {
|
||||
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
|
||||
final List<CacheHit> variants) throws IOException, HttpException {
|
||||
final Map<String, CacheHit> variantMap = new HashMap<>();
|
||||
for (final CacheHit variant : variants) {
|
||||
final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (header != null) {
|
||||
variantMap.put(header.getValue(), variant);
|
||||
}
|
||||
}
|
||||
|
||||
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variantMap.keySet());
|
||||
|
||||
final Instant requestDate = getCurrentDate();
|
||||
final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
|
||||
|
@ -507,15 +522,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
}
|
||||
|
||||
final String resultEtag = resultEtagHeader.getValue();
|
||||
final Variant matchingVariant = variants.get(resultEtag);
|
||||
if (matchingVariant == null) {
|
||||
final CacheHit match = variantMap.get(resultEtag);
|
||||
if (match == null) {
|
||||
LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
|
||||
return callBackend(target, request, scope, chain);
|
||||
}
|
||||
|
||||
final HttpCacheEntry variantEntry = matchingVariant.getEntry();
|
||||
|
||||
if (revalidationResponseIsTooOld(backendResponse, variantEntry)
|
||||
if (revalidationResponseIsTooOld(backendResponse, match.entry)
|
||||
&& (request.getEntity() == null || request.getEntity().isRepeatable())) {
|
||||
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
|
||||
return callBackend(target, unconditional, scope, chain);
|
||||
|
@ -523,13 +536,12 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
|
||||
recordCacheUpdate(scope.clientContext);
|
||||
|
||||
final HttpCacheEntry responseEntry = responseCache.updateVariantEntry(
|
||||
target, conditionalRequest, backendResponse, variantEntry, requestDate, responseDate);
|
||||
if (shouldSendNotModifiedResponse(request, responseEntry)) {
|
||||
return convert(responseGenerator.generateNotModifiedResponse(responseEntry), scope);
|
||||
final CacheHit hit = responseCache.update(match, backendResponse, requestDate, responseDate);
|
||||
if (shouldSendNotModifiedResponse(request, hit.entry)) {
|
||||
return convert(responseGenerator.generateNotModifiedResponse(hit.entry), scope);
|
||||
}
|
||||
final SimpleHttpResponse response = responseGenerator.generateResponse(request, responseEntry);
|
||||
responseCache.reuseVariantEntryFor(target, request, backendResponse, responseEntry, requestDate, responseDate);
|
||||
final SimpleHttpResponse response = responseGenerator.generateResponse(request, hit.entry);
|
||||
responseCache.storeReusing(hit, target, request, backendResponse, requestDate, responseDate);
|
||||
return convert(response, scope);
|
||||
} catch (final IOException | RuntimeException ex) {
|
||||
backendResponse.close();
|
||||
|
|
|
@ -185,9 +185,9 @@ public class CachingExecBase {
|
|||
|
||||
SimpleHttpResponse generateCachedResponse(
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final HttpCacheEntry entry,
|
||||
final HttpRequest request,
|
||||
final HttpContext context,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant now) throws ResourceIOException {
|
||||
final SimpleHttpResponse cachedResponse;
|
||||
if (request.containsHeader(HttpHeaders.IF_NONE_MATCH)
|
||||
|
@ -223,9 +223,9 @@ public class CachingExecBase {
|
|||
SimpleHttpResponse handleRevalidationFailure(
|
||||
final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final HttpCacheEntry entry,
|
||||
final HttpRequest request,
|
||||
final HttpContext context,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant now) throws IOException {
|
||||
if (staleResponseNotAllowed(requestCacheControl, responseCacheControl, entry, now)) {
|
||||
return generateGatewayTimeout(context);
|
||||
|
@ -259,8 +259,8 @@ public class CachingExecBase {
|
|||
|| explicitFreshnessRequest(requestCacheControl, responseCacheControl, entry, now);
|
||||
}
|
||||
|
||||
boolean mayCallBackend(final RequestCacheControl cacheControl) {
|
||||
if (cacheControl.isOnlyIfCached()) {
|
||||
boolean mayCallBackend(final RequestCacheControl requestCacheControl) {
|
||||
if (requestCacheControl.isOnlyIfCached()) {
|
||||
LOG.debug("Request marked only-if-cached");
|
||||
return false;
|
||||
}
|
||||
|
@ -268,7 +268,8 @@ public class CachingExecBase {
|
|||
}
|
||||
|
||||
boolean explicitFreshnessRequest(final RequestCacheControl requestCacheControl,
|
||||
final ResponseCacheControl responseCacheControl, final HttpCacheEntry entry,
|
||||
final ResponseCacheControl responseCacheControl,
|
||||
final HttpCacheEntry entry,
|
||||
final Instant now) {
|
||||
if (requestCacheControl.getMaxStale() >= 0) {
|
||||
final TimeValue age = validityPolicy.getCurrentAge(entry, now);
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HeaderConstants;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
|
@ -84,9 +84,9 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
|
|||
* @param variants
|
||||
* @return the wrapped request
|
||||
*/
|
||||
public T buildConditionalRequestFromVariants(final T request, final Map<String, Variant> variants) {
|
||||
public T buildConditionalRequestFromVariants(final T request, final Set<String> variants) {
|
||||
final T newRequest = messageCopier.create(request);
|
||||
newRequest.setHeader(MessageSupport.format(HttpHeaders.IF_NONE_MATCH, variants.keySet()));
|
||||
newRequest.setHeader(MessageSupport.format(HttpHeaders.IF_NONE_MATCH, variants));
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.core5.concurrent.Cancellable;
|
||||
|
@ -39,7 +39,64 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
|
|||
|
||||
interface HttpAsyncCache {
|
||||
|
||||
String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
|
||||
/**
|
||||
* Returns a result with either a fully matching {@link HttpCacheEntry}
|
||||
* a partial match with a list of known variants or null if no match could be found.
|
||||
*/
|
||||
Cancellable match(HttpHost host, HttpRequest request, FutureCallback<CacheMatch> callback);
|
||||
|
||||
/**
|
||||
* Retrieves variant {@link HttpCacheEntry}s for the given hit.
|
||||
*/
|
||||
Cancellable getVariants(
|
||||
CacheHit hit, FutureCallback<Collection<CacheHit>> callback);
|
||||
|
||||
/**
|
||||
* Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache.
|
||||
*/
|
||||
Cancellable store(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
ByteArrayBuffer content,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<CacheHit> callback);
|
||||
|
||||
/**
|
||||
* Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse} and
|
||||
* updates the root entry if the given cache entry represents a variant.
|
||||
*/
|
||||
Cancellable update(
|
||||
CacheHit stale,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<CacheHit> callback);
|
||||
|
||||
/**
|
||||
* Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse}.
|
||||
*/
|
||||
Cancellable update(
|
||||
CacheHit stale,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<CacheHit> callback);
|
||||
|
||||
/**
|
||||
* Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache
|
||||
* re-using the resource of the existing {@link HttpCacheEntry}.
|
||||
*/
|
||||
Cancellable storeReusing(
|
||||
CacheHit hit,
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<CacheHit> callback);
|
||||
|
||||
/**
|
||||
* Clear all matching {@link HttpCacheEntry}s.
|
||||
|
@ -59,65 +116,4 @@ interface HttpAsyncCache {
|
|||
Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
HttpHost host, HttpRequest request, HttpResponse response, FutureCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Retrieve matching {@link HttpCacheEntry} from the cache if it exists
|
||||
*/
|
||||
Cancellable getCacheEntry(
|
||||
HttpHost host, HttpRequest request, FutureCallback<HttpCacheEntry> callback);
|
||||
|
||||
/**
|
||||
* Retrieve all variants from the cache, if there are no variants then an empty
|
||||
*/
|
||||
Cancellable getVariantCacheEntriesWithEtags(
|
||||
HttpHost host, HttpRequest request, FutureCallback<Map<String,Variant>> callback);
|
||||
|
||||
/**
|
||||
* Store a {@link HttpResponse} in the cache if possible, and return
|
||||
*/
|
||||
Cancellable createEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
ByteArrayBuffer content,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<HttpCacheEntry> callback);
|
||||
|
||||
/**
|
||||
* Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
|
||||
*/
|
||||
Cancellable updateEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpCacheEntry stale,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<HttpCacheEntry> callback);
|
||||
|
||||
/**
|
||||
* Update a specific {@link HttpCacheEntry} representing a cached variant
|
||||
* using a 304 {@link HttpResponse}.
|
||||
*/
|
||||
Cancellable updateVariantEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
HttpCacheEntry entry,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<HttpCacheEntry> callback);
|
||||
|
||||
/**
|
||||
* Specifies cache should reuse the given cached variant to satisfy
|
||||
* requests whose varying headers match those of the given client request.
|
||||
*/
|
||||
Cancellable reuseVariantEntryFor(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
HttpCacheEntry entry,
|
||||
Instant requestSent,
|
||||
Instant responseReceived,
|
||||
FutureCallback<Boolean> callback);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
|
@ -37,7 +37,59 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
|
|||
|
||||
interface HttpCache {
|
||||
|
||||
String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
|
||||
/**
|
||||
* Returns a result with either a fully matching {@link HttpCacheEntry}
|
||||
* a partial match with a list of known variants or null if no match could be found.
|
||||
*/
|
||||
CacheMatch match(HttpHost host, HttpRequest request);
|
||||
|
||||
/**
|
||||
* Retrieves variant {@link HttpCacheEntry}s for the given hit.
|
||||
*/
|
||||
List<CacheHit> getVariants(CacheHit hit);
|
||||
|
||||
/**
|
||||
* Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache.
|
||||
*/
|
||||
CacheHit store(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
ByteArrayBuffer content,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse} and
|
||||
* updates the root entry if the given cache entry represents a variant.
|
||||
*/
|
||||
CacheHit update(
|
||||
CacheHit stale,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Updates {@link HttpCacheEntry} using details from a 304 {@link HttpResponse}.
|
||||
*/
|
||||
CacheHit update(
|
||||
CacheHit stale,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Stores {@link HttpRequest} / {@link HttpResponse} exchange details in the cache
|
||||
* re-using the resource of the existing {@link HttpCacheEntry}.
|
||||
*/
|
||||
CacheHit storeReusing(
|
||||
CacheHit hit,
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Clear all matching {@link HttpCacheEntry}s.
|
||||
|
@ -54,60 +106,4 @@ interface HttpCache {
|
|||
*/
|
||||
void flushCacheEntriesInvalidatedByExchange(HttpHost host, HttpRequest request, HttpResponse response);
|
||||
|
||||
/**
|
||||
* Retrieve matching {@link HttpCacheEntry} from the cache if it exists.
|
||||
*/
|
||||
HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request);
|
||||
|
||||
/**
|
||||
* Retrieve all variants from the cache, if there are no variants then an empty
|
||||
* {@link Map} is returned
|
||||
*/
|
||||
Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request);
|
||||
|
||||
/**
|
||||
* Store a {@link HttpResponse} in the cache if possible, and return
|
||||
*/
|
||||
HttpCacheEntry createEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
ByteArrayBuffer content,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
|
||||
*/
|
||||
HttpCacheEntry updateEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpCacheEntry stale,
|
||||
HttpResponse originResponse,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Update a specific {@link HttpCacheEntry} representing a cached variant
|
||||
* using a 304 {@link HttpResponse}.
|
||||
*/
|
||||
HttpCacheEntry updateVariantEntry(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
HttpCacheEntry entry,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Specifies cache should reuse the given cached variant to satisfy
|
||||
* requests whose varying headers match those of the given client request.
|
||||
*/
|
||||
void reuseVariantEntryFor(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse originResponse,
|
||||
HttpCacheEntry entry,
|
||||
Instant requestSent,
|
||||
Instant responseReceived);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class ContainsHeaderMatcher extends BaseMatcher<MessageHeaders> {
|
|||
|
||||
@Override
|
||||
public void describeTo(final Description description) {
|
||||
description.appendText("contains header ").appendValue(headerValue).appendText(": ").appendValue(headerValue);
|
||||
description.appendText("contains header ").appendValue(headerName).appendText(": ").appendValue(headerValue);
|
||||
}
|
||||
|
||||
public static Matcher<MessageHeaders> contains(final String headerName, final Object headerValue) {
|
||||
|
|
|
@ -318,6 +318,11 @@ public class HttpTestUtils {
|
|||
return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variantMap);
|
||||
}
|
||||
|
||||
public static HttpCacheEntry makeCacheEntry(final Map<String, String> variantMap) {
|
||||
final Instant now = Instant.now();
|
||||
return makeCacheEntry(now, now, new Header[] {}, variantMap);
|
||||
}
|
||||
|
||||
public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {
|
||||
final Instant now = Instant.now();
|
||||
return makeCacheEntry(now, now, headers, bytes);
|
||||
|
|
|
@ -34,15 +34,15 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpHead;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpOptions;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpTrace;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
|
@ -52,6 +52,7 @@ import org.apache.hc.core5.http.HttpStatus;
|
|||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -67,64 +68,7 @@ public class TestBasicHttpCache {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushCacheEntriesOnGet() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpGet("/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesFor(host, req);
|
||||
|
||||
assertEquals(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushCacheEntriesOnHead() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpHead("/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesFor(host, req);
|
||||
|
||||
assertEquals(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushCacheEntriesOnOptions() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpOptions("/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesFor(host, req);
|
||||
|
||||
assertEquals(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushCacheEntriesOnTrace() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpTrace("/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesFor(host, req);
|
||||
|
||||
assertEquals(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushContentLocationEntryIfUnSafeRequest()
|
||||
throws Exception {
|
||||
public void testFlushContentLocationEntryIfUnSafeRequest() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpPost("/foo");
|
||||
final HttpResponse resp = HttpTestUtils.make200Response();
|
||||
|
@ -144,8 +88,7 @@ public class TestBasicHttpCache {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushContentLocationEntryIfSafeRequest()
|
||||
throws Exception {
|
||||
public void testDoNotFlushContentLocationEntryIfSafeRequest() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpGet("/foo");
|
||||
final HttpResponse resp = HttpTestUtils.make200Response();
|
||||
|
@ -187,7 +130,7 @@ public class TestBasicHttpCache {
|
|||
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
|
||||
impl.storeInCache(req, resp, Instant.now(), Instant.now(), key, entry);
|
||||
impl.store(req, resp, Instant.now(), Instant.now(), key, entry);
|
||||
assertSame(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
|
@ -195,7 +138,7 @@ public class TestBasicHttpCache {
|
|||
public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
final HttpCacheEntry result = impl.getCacheEntry(host, request);
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
|
@ -210,8 +153,10 @@ public class TestBasicHttpCache {
|
|||
|
||||
backing.map.put(key,entry);
|
||||
|
||||
final HttpCacheEntry result = impl.getCacheEntry(host, request);
|
||||
assertSame(entry, result);
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.hit);
|
||||
assertSame(entry, result.hit.entry);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -229,11 +174,12 @@ public class TestBasicHttpCache {
|
|||
origResponse.setHeader("Vary", "Accept-Encoding");
|
||||
origResponse.setHeader("Content-Encoding","gzip");
|
||||
|
||||
impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
final HttpCacheEntry result = impl.getCacheEntry(host, request);
|
||||
assertNull(result);
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
assertNotNull(result);
|
||||
assertNull(result.hit);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -251,12 +197,13 @@ public class TestBasicHttpCache {
|
|||
origResponse.setHeader("Vary", "Accept-Encoding");
|
||||
origResponse.setHeader("Content-Encoding","gzip");
|
||||
|
||||
impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
final HttpCacheEntry result = impl.getCacheEntry(host, request);
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.hit);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -282,28 +229,41 @@ public class TestBasicHttpCache {
|
|||
origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
|
||||
|
||||
// Store the two variants in cache
|
||||
impl.createEntry(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
|
||||
impl.createEntry(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
request.setHeader("Accept-Encoding", "gzip");
|
||||
final HttpCacheEntry result = impl.getCacheEntry(host, request);
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.hit);
|
||||
final HttpCacheEntry entry = result.hit.entry;
|
||||
assertNotNull(entry);
|
||||
|
||||
// Retrieve the ETag header value from the original response and assert that
|
||||
// the returned cache entry has the same ETag value
|
||||
final String expectedEtag = origResponse2.getFirstHeader(HttpHeaders.ETAG).getValue();
|
||||
final String actualEtag = result.getFirstHeader(HttpHeaders.ETAG).getValue();
|
||||
final String actualEtag = entry.getFirstHeader(HttpHeaders.ETAG).getValue();
|
||||
|
||||
assertEquals(expectedEtag, actualEtag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantCacheEntriesReturnsEmptySetOnNoVariants() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
public void testGetVariantsRootNoVariants() throws Exception {
|
||||
final HttpCacheEntry root = HttpTestUtils.makeCacheEntry();
|
||||
final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
|
||||
|
||||
final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, request);
|
||||
assertNotNull(variants);
|
||||
assertEquals(0, variants.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantsRootNonExistentVariants() throws Exception {
|
||||
final Map<String, String> variantMap = new HashMap<>();
|
||||
variantMap.put("variant1", "variant-key-1");
|
||||
variantMap.put("variant2", "variant-key-2");
|
||||
final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(variantMap);
|
||||
final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
|
||||
|
||||
assertNotNull(variants);
|
||||
assertEquals(0, variants.size());
|
||||
|
@ -334,14 +294,21 @@ public class TestBasicHttpCache {
|
|||
resp2.setHeader("Content-Encoding","gzip");
|
||||
resp2.setHeader("Vary", "Accept-Encoding");
|
||||
|
||||
impl.createEntry(host, req1, resp1, null, Instant.now(), Instant.now());
|
||||
impl.createEntry(host, req2, resp2, null, Instant.now(), Instant.now());
|
||||
final CacheHit hit1 = impl.store(host, req1, resp1, null, Instant.now(), Instant.now());
|
||||
final CacheHit hit2 = impl.store(host, req2, resp2, null, Instant.now(), Instant.now());
|
||||
|
||||
final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, req1);
|
||||
final Map<String, String> variantMap = new HashMap<>();
|
||||
variantMap.put("variant-1", hit1.variantKey);
|
||||
variantMap.put("variant-2", hit2.variantKey);
|
||||
|
||||
final Map<String, HttpCacheEntry> variants = impl.getVariants(new CacheHit(hit1.rootKey,
|
||||
HttpTestUtils.makeCacheEntry(variantMap))).stream()
|
||||
.collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry));
|
||||
|
||||
assertNotNull(variants);
|
||||
assertEquals(2, variants.size());
|
||||
|
||||
MatcherAssert.assertThat(variants.get(hit1.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit1.entry));
|
||||
MatcherAssert.assertThat(variants.get(hit2.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit2.entry));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ public class TestCachingExecChain {
|
|||
execute(req2);
|
||||
|
||||
Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
|
||||
Mockito.verify(cache).createEntry(Mockito.eq(host), RequestEquivalent.eq(req1),
|
||||
Mockito.verify(cache).store(Mockito.eq(host), RequestEquivalent.eq(req1),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
|
@ -1070,7 +1070,7 @@ public class TestCachingExecChain {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
|
||||
impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
|
||||
|
||||
Mockito.verify(cache, Mockito.never()).createEntry(
|
||||
Mockito.verify(cache, Mockito.never()).store(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
|
@ -1096,18 +1096,18 @@ public class TestCachingExecChain {
|
|||
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
|
||||
|
||||
Mockito.when(mockCache.createEntry(
|
||||
Mockito.when(mockCache.store(
|
||||
Mockito.eq(host),
|
||||
RequestEquivalent.eq(request),
|
||||
ResponseEquivalent.eq(response),
|
||||
Mockito.any(),
|
||||
Mockito.eq(requestSent),
|
||||
Mockito.eq(responseReceived))).thenReturn(httpCacheEntry);
|
||||
Mockito.eq(responseReceived))).thenReturn(new CacheHit("key", httpCacheEntry));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
|
||||
impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
|
||||
|
||||
Mockito.verify(mockCache).createEntry(
|
||||
Mockito.verify(mockCache).store(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
|
@ -1426,7 +1426,8 @@ public class TestCachingExecChain {
|
|||
|
||||
// Prepare original cache entry
|
||||
final HttpCacheEntry originalEntry = HttpTestUtils.makeCacheEntry();
|
||||
Mockito.when(mockCache.getCacheEntry(host, request)).thenReturn(originalEntry);
|
||||
Mockito.when(mockCache.match(host, request)).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", originalEntry), null));
|
||||
|
||||
// Prepare 304 Not Modified response
|
||||
final Instant now = Instant.now();
|
||||
|
@ -1452,19 +1453,19 @@ public class TestCachingExecChain {
|
|||
headers,
|
||||
new HeapResource(body.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
Mockito.when(mockCache.updateEntry(Mockito.eq(host), Mockito.eq(request), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(cacheEntry);
|
||||
Mockito.when(mockCache.update(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
|
||||
.thenReturn(new CacheHit("key", cacheEntry));
|
||||
|
||||
// Call cacheAndReturnResponse with 304 Not Modified response
|
||||
final ClassicHttpResponse cachedResponse = impl.cacheAndReturnResponse(host, request, backendResponse, scope, requestSent, responseReceived);
|
||||
|
||||
// Verify cache entry is updated
|
||||
Mockito.verify(mockCache).updateEntry(
|
||||
host,
|
||||
request,
|
||||
originalEntry,
|
||||
backendResponse,
|
||||
requestSent,
|
||||
responseReceived
|
||||
Mockito.verify(mockCache).update(
|
||||
Mockito.any(),
|
||||
Mockito.same(request),
|
||||
Mockito.same(backendResponse),
|
||||
Mockito.eq(requestSent),
|
||||
Mockito.eq(responseReceived)
|
||||
);
|
||||
|
||||
// Verify response is generated from the updated cache entry
|
||||
|
@ -1518,7 +1519,8 @@ public class TestCachingExecChain {
|
|||
// Execute the first request and assert the response
|
||||
final ClassicHttpResponse response1 = execute(req1);
|
||||
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
Mockito.when(responseCache.getCacheEntry(Mockito.any(), Mockito.any())).thenReturn(httpCacheEntry);
|
||||
Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", httpCacheEntry), null));
|
||||
Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
|
||||
|
||||
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
||||
|
@ -1619,7 +1621,8 @@ public class TestCachingExecChain {
|
|||
// Execute the first request and assert the response
|
||||
final ClassicHttpResponse response1 = execute(req1);
|
||||
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
Mockito.when(responseCache.getCacheEntry(Mockito.any(), Mockito.any())).thenReturn(httpCacheEntry);
|
||||
Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", httpCacheEntry), null));
|
||||
Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
|
||||
|
||||
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
|
@ -280,10 +281,7 @@ public class TestConditionalRequestBuilder {
|
|||
final String etag2 = "\"456\"";
|
||||
final String etag3 = "\"789\"";
|
||||
|
||||
final Map<String,Variant> variantEntries = new HashMap<>();
|
||||
variantEntries.put(etag1, new Variant("A", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag1))));
|
||||
variantEntries.put(etag2, new Variant("B", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag2))));
|
||||
variantEntries.put(etag3, new Variant("C", HttpTestUtils.makeCacheEntry(new BasicHeader("ETag", etag3))));
|
||||
final Set<String> variantEntries = new HashSet<>(Arrays.asList(etag1, etag2, etag3));
|
||||
|
||||
final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
|
||||
|
||||
|
|
|
@ -1848,24 +1848,23 @@ public class TestProtocolRequirements {
|
|||
notModified.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
notModified.setHeader("ETag", "\"etag\"");
|
||||
|
||||
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
|
||||
Mockito.when(mockCache.updateEntry(
|
||||
Mockito.eq(host),
|
||||
RequestEquivalent.eq(request),
|
||||
Mockito.eq(entry),
|
||||
ResponseEquivalent.eq(notModified),
|
||||
Mockito.any(),
|
||||
Mockito.any()))
|
||||
.thenReturn(HttpTestUtils.makeCacheEntry());
|
||||
Mockito.when(mockCache.update(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any()))
|
||||
.thenReturn(new CacheHit("key", HttpTestUtils.makeCacheEntry()));
|
||||
|
||||
execute(request);
|
||||
|
||||
Mockito.verify(mockCache).updateEntry(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.verify(mockCache).update(
|
||||
Mockito.any(),
|
||||
RequestEquivalent.eq(request),
|
||||
ResponseEquivalent.eq(notModified),
|
||||
Mockito.any(),
|
||||
Mockito.any());
|
||||
}
|
||||
|
@ -1892,7 +1891,8 @@ public class TestProtocolRequirements {
|
|||
impl = new CachingExec(mockCache, null, config);
|
||||
request = new BasicClassicHttpRequest("GET", "/thing");
|
||||
|
||||
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
|
@ -1936,7 +1936,8 @@ public class TestProtocolRequirements {
|
|||
impl = new CachingExec(mockCache, null, config);
|
||||
request = new BasicClassicHttpRequest("GET", "/thing");
|
||||
|
||||
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(
|
||||
new IOException("can't talk to origin!"));
|
||||
|
||||
|
@ -2120,7 +2121,8 @@ public class TestProtocolRequirements {
|
|||
impl = new CachingExec(mockCache, null, config);
|
||||
request = new BasicClassicHttpRequest("GET", "/thing");
|
||||
|
||||
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
|
@ -2183,15 +2185,16 @@ public class TestProtocolRequirements {
|
|||
|
||||
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(validated);
|
||||
Mockito.when(mockCache.createEntry(
|
||||
Mockito.when(mockCache.store(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
ResponseEquivalent.eq(validated),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any())).thenReturn(cacheEntry);
|
||||
Mockito.any())).thenReturn(new CacheHit("key", cacheEntry));
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
|
@ -2214,7 +2217,7 @@ public class TestProtocolRequirements {
|
|||
}
|
||||
Assertions.assertTrue(found113Warning);
|
||||
}
|
||||
Mockito.verify(mockCache).createEntry(
|
||||
Mockito.verify(mockCache).store(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
|
|
Loading…
Reference in New Issue