1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.cache;
28
29 import java.io.IOException;
30 import java.io.InterruptedIOException;
31 import java.nio.ByteBuffer;
32 import java.time.Instant;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.concurrent.ScheduledExecutorService;
38 import java.util.concurrent.atomic.AtomicBoolean;
39 import java.util.concurrent.atomic.AtomicReference;
40 import java.util.function.Consumer;
41
42 import org.apache.hc.client5.http.HttpRoute;
43 import org.apache.hc.client5.http.async.AsyncExecCallback;
44 import org.apache.hc.client5.http.async.AsyncExecChain;
45 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
46 import org.apache.hc.client5.http.async.methods.SimpleBody;
47 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
48 import org.apache.hc.client5.http.cache.CacheResponseStatus;
49 import org.apache.hc.client5.http.cache.HttpCacheContext;
50 import org.apache.hc.client5.http.cache.HttpCacheEntry;
51 import org.apache.hc.client5.http.cache.RequestCacheControl;
52 import org.apache.hc.client5.http.cache.ResourceIOException;
53 import org.apache.hc.client5.http.cache.ResponseCacheControl;
54 import org.apache.hc.client5.http.impl.ExecSupport;
55 import org.apache.hc.client5.http.protocol.HttpClientContext;
56 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
57 import org.apache.hc.client5.http.validator.ETag;
58 import org.apache.hc.core5.annotation.Contract;
59 import org.apache.hc.core5.annotation.ThreadingBehavior;
60 import org.apache.hc.core5.concurrent.CancellableDependency;
61 import org.apache.hc.core5.concurrent.ComplexFuture;
62 import org.apache.hc.core5.concurrent.FutureCallback;
63 import org.apache.hc.core5.http.ContentType;
64 import org.apache.hc.core5.http.EntityDetails;
65 import org.apache.hc.core5.http.Header;
66 import org.apache.hc.core5.http.HttpException;
67 import org.apache.hc.core5.http.HttpHeaders;
68 import org.apache.hc.core5.http.HttpHost;
69 import org.apache.hc.core5.http.HttpRequest;
70 import org.apache.hc.core5.http.HttpResponse;
71 import org.apache.hc.core5.http.HttpStatus;
72 import org.apache.hc.core5.http.impl.BasicEntityDetails;
73 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
74 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
75 import org.apache.hc.core5.http.nio.CapacityChannel;
76 import org.apache.hc.core5.http.support.BasicRequestBuilder;
77 import org.apache.hc.core5.net.URIAuthority;
78 import org.apache.hc.core5.util.Args;
79 import org.apache.hc.core5.util.ByteArrayBuffer;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82
83
84
85
86
87
88
89
90
91
92
93
94 @Contract(threading = ThreadingBehavior.SAFE)
95 class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler {
96
97 private static final Logger LOG = LoggerFactory.getLogger(AsyncCachingExec.class);
98 private final HttpAsyncCache responseCache;
99 private final DefaultAsyncCacheRevalidator cacheRevalidator;
100 private final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder;
101
102 AsyncCachingExec(final HttpAsyncCache cache, final DefaultAsyncCacheRevalidator cacheRevalidator, final CacheConfig config) {
103 super(config);
104 this.responseCache = Args.notNull(cache, "Response cache");
105 this.cacheRevalidator = cacheRevalidator;
106 this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(request ->
107 BasicRequestBuilder.copy(request).build());
108 }
109
110 AsyncCachingExec(
111 final HttpAsyncCache cache,
112 final ScheduledExecutorService executorService,
113 final SchedulingStrategy schedulingStrategy,
114 final CacheConfig config) {
115 this(cache,
116 executorService != null ? new DefaultAsyncCacheRevalidator(executorService, schedulingStrategy) : null,
117 config);
118 }
119
120 private void triggerResponse(
121 final SimpleHttpResponse cacheResponse,
122 final AsyncExecChain.Scope scope,
123 final AsyncExecCallback asyncExecCallback) {
124 scope.execRuntime.releaseEndpoint();
125
126 final SimpleBody body = cacheResponse.getBody();
127 final byte[] content = body != null ? body.getBodyBytes() : null;
128 final ContentType contentType = body != null ? body.getContentType() : null;
129 try {
130 final AsyncDataConsumer dataConsumer = asyncExecCallback.handleResponse(
131 cacheResponse,
132 content != null ? new BasicEntityDetails(content.length, contentType) : null);
133 if (dataConsumer != null) {
134 if (content != null) {
135 dataConsumer.consume(ByteBuffer.wrap(content));
136 }
137 dataConsumer.streamEnd(null);
138 }
139 asyncExecCallback.completed();
140 } catch (final HttpException | IOException ex) {
141 asyncExecCallback.failed(ex);
142 }
143 }
144
145 static class AsyncExecCallbackWrapper implements AsyncExecCallback {
146
147 private final Runnable command;
148 private final Consumer<Exception> exceptionConsumer;
149
150 AsyncExecCallbackWrapper(final Runnable command, final Consumer<Exception> exceptionConsumer) {
151 this.command = command;
152 this.exceptionConsumer = exceptionConsumer;
153 }
154
155 @Override
156 public AsyncDataConsumer handleResponse(
157 final HttpResponse response,
158 final EntityDetails entityDetails) throws HttpException, IOException {
159 return null;
160 }
161
162 @Override
163 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
164 }
165
166 @Override
167 public void completed() {
168 command.run();
169 }
170
171 @Override
172 public void failed(final Exception cause) {
173 if (exceptionConsumer != null) {
174 exceptionConsumer.accept(cause);
175 }
176 }
177
178 }
179
180 @Override
181 public void execute(
182 final HttpRequest request,
183 final AsyncEntityProducer entityProducer,
184 final AsyncExecChain.Scope scope,
185 final AsyncExecChain chain,
186 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
187 Args.notNull(request, "HTTP request");
188 Args.notNull(scope, "Scope");
189
190 final HttpRoute route = scope.route;
191 final HttpClientContext context = scope.clientContext;
192
193 final URIAuthority authority = request.getAuthority();
194 final String scheme = request.getScheme();
195 final HttpHost target = authority != null ? new HttpHost(scheme, authority) : route.getTargetHost();
196 doExecute(target,
197 request,
198 entityProducer,
199 scope,
200 chain,
201 new AsyncExecCallback() {
202
203 @Override
204 public AsyncDataConsumer handleResponse(
205 final HttpResponse response,
206 final EntityDetails entityDetails) throws HttpException, IOException {
207 context.setRequest(request);
208 context.setResponse(response);
209 return asyncExecCallback.handleResponse(response, entityDetails);
210 }
211
212 @Override
213 public void handleInformationResponse(
214 final HttpResponse response) throws HttpException, IOException {
215 asyncExecCallback.handleInformationResponse(response);
216 }
217
218 @Override
219 public void completed() {
220 asyncExecCallback.completed();
221 }
222
223 @Override
224 public void failed(final Exception cause) {
225 asyncExecCallback.failed(cause);
226 }
227
228 });
229 }
230
231 public void doExecute(
232 final HttpHost target,
233 final HttpRequest request,
234 final AsyncEntityProducer entityProducer,
235 final AsyncExecChain.Scope scope,
236 final AsyncExecChain chain,
237 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
238
239 final String exchangeId = scope.exchangeId;
240 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
241 final CancellableDependency operation = scope.cancellableDependency;
242
243 if (LOG.isDebugEnabled()) {
244 LOG.debug("{} request via cache: {} {}", exchangeId, request.getMethod(), request.getRequestUri());
245 }
246
247 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MISS);
248 context.setCacheEntry(null);
249
250 if (clientRequestsOurOptions(request)) {
251 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
252 triggerResponse(SimpleHttpResponse.create(HttpStatus.SC_NOT_IMPLEMENTED), scope, asyncExecCallback);
253 return;
254 }
255
256 final RequestCacheControl requestCacheControl;
257 if (request.containsHeader(HttpHeaders.CACHE_CONTROL)) {
258 requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
259 context.setRequestCacheControl(requestCacheControl);
260 } else {
261 requestCacheControl = context.getRequestCacheControlOrDefault();
262 CacheControlHeaderGenerator.INSTANCE.generate(requestCacheControl, request);
263 }
264
265 if (LOG.isDebugEnabled()) {
266 LOG.debug("{} request cache control: {}", exchangeId, requestCacheControl);
267 }
268
269 if (cacheableRequestPolicy.canBeServedFromCache(requestCacheControl, request)) {
270 operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
271
272 @Override
273 public void completed(final CacheMatch result) {
274 final CacheHit hit = result != null ? result.hit : null;
275 final CacheHit root = result != null ? result.root : null;
276 if (hit == null) {
277 handleCacheMiss(requestCacheControl, root, target, request, entityProducer, scope, chain, asyncExecCallback);
278 } else {
279 final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(hit.entry);
280 if (LOG.isDebugEnabled()) {
281 LOG.debug("{} response cache control: {}", exchangeId, responseCacheControl);
282 }
283 context.setResponseCacheControl(responseCacheControl);
284 handleCacheHit(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
285 }
286 }
287
288 @Override
289 public void failed(final Exception cause) {
290 asyncExecCallback.failed(cause);
291 }
292
293 @Override
294 public void cancelled() {
295 asyncExecCallback.failed(new InterruptedIOException());
296 }
297
298 }));
299
300 } else {
301 if (LOG.isDebugEnabled()) {
302 LOG.debug("{} request cannot be served from cache", exchangeId);
303 }
304 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
305 }
306 }
307
308 void chainProceed(
309 final HttpRequest request,
310 final AsyncEntityProducer entityProducer,
311 final AsyncExecChain.Scope scope,
312 final AsyncExecChain chain,
313 final AsyncExecCallback asyncExecCallback) {
314 try {
315 chain.proceed(request, entityProducer, scope, asyncExecCallback);
316 } catch (final HttpException | IOException ex) {
317 asyncExecCallback.failed(ex);
318 }
319 }
320
321 void callBackend(
322 final HttpHost target,
323 final HttpRequest request,
324 final AsyncEntityProducer entityProducer,
325 final AsyncExecChain.Scope scope,
326 final AsyncExecChain chain,
327 final AsyncExecCallback asyncExecCallback) {
328 final String exchangeId = scope.exchangeId;
329
330 if (LOG.isDebugEnabled()) {
331 LOG.debug("{} calling the backend", exchangeId);
332 }
333 final Instant requestDate = getCurrentDate();
334 final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
335 chainProceed(request, entityProducer, scope, chain, new AsyncExecCallback() {
336
337 @Override
338 public AsyncDataConsumer handleResponse(
339 final HttpResponse backendResponse,
340 final EntityDetails entityDetails) throws HttpException, IOException {
341 final Instant responseDate = getCurrentDate();
342 final AsyncExecCallback callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
343 callbackRef.set(callback);
344 return callback.handleResponse(backendResponse, entityDetails);
345 }
346
347 @Override
348 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
349 final AsyncExecCallback callback = callbackRef.getAndSet(null);
350 if (callback != null) {
351 callback.handleInformationResponse(response);
352 } else {
353 asyncExecCallback.handleInformationResponse(response);
354 }
355 }
356
357 @Override
358 public void completed() {
359 final AsyncExecCallback callback = callbackRef.getAndSet(null);
360 if (callback != null) {
361 callback.completed();
362 } else {
363 asyncExecCallback.completed();
364 }
365 }
366
367 @Override
368 public void failed(final Exception cause) {
369 final AsyncExecCallback callback = callbackRef.getAndSet(null);
370 if (callback != null) {
371 callback.failed(cause);
372 } else {
373 asyncExecCallback.failed(cause);
374 }
375 }
376
377 });
378 }
379
380 class CachingAsyncDataConsumer implements AsyncDataConsumer {
381
382 private final String exchangeId;
383 private final AsyncExecCallback fallback;
384 private final HttpResponse backendResponse;
385 private final EntityDetails entityDetails;
386 private final AtomicBoolean writtenThrough;
387 private final AtomicReference<ByteArrayBuffer> bufferRef;
388 private final AtomicReference<AsyncDataConsumer> dataConsumerRef;
389
390 CachingAsyncDataConsumer(
391 final String exchangeId,
392 final AsyncExecCallback fallback,
393 final HttpResponse backendResponse,
394 final EntityDetails entityDetails) {
395 this.exchangeId = exchangeId;
396 this.fallback = fallback;
397 this.backendResponse = backendResponse;
398 this.entityDetails = entityDetails;
399 this.writtenThrough = new AtomicBoolean(false);
400 this.bufferRef = new AtomicReference<>(entityDetails != null ? new ByteArrayBuffer(1024) : null);
401 this.dataConsumerRef = new AtomicReference<>();
402 }
403
404 @Override
405 public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
406 final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
407 if (dataConsumer != null) {
408 dataConsumer.updateCapacity(capacityChannel);
409 } else {
410 capacityChannel.update(Integer.MAX_VALUE);
411 }
412 }
413
414 @Override
415 public final void consume(final ByteBuffer src) throws IOException {
416 final ByteArrayBuffer buffer = bufferRef.get();
417 if (buffer != null) {
418 if (src.hasArray()) {
419 buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
420 } else {
421 while (src.hasRemaining()) {
422 buffer.append(src.get());
423 }
424 }
425 if (buffer.length() > cacheConfig.getMaxObjectSize()) {
426 if (LOG.isDebugEnabled()) {
427 LOG.debug("{} backend response content length exceeds maximum", exchangeId);
428 }
429
430
431 bufferRef.set(null);
432 try {
433 final AsyncDataConsumer dataConsumer = fallback.handleResponse(backendResponse, entityDetails);
434 if (dataConsumer != null) {
435 dataConsumerRef.set(dataConsumer);
436 writtenThrough.set(true);
437 dataConsumer.consume(ByteBuffer.wrap(buffer.array(), 0, buffer.length()));
438 }
439 } catch (final HttpException ex) {
440 fallback.failed(ex);
441 }
442 }
443 } else {
444 final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
445 if (dataConsumer != null) {
446 dataConsumer.consume(src);
447 }
448 }
449 }
450
451 @Override
452 public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
453 final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
454 if (dataConsumer != null) {
455 dataConsumer.streamEnd(trailers);
456 }
457 }
458
459 @Override
460 public void releaseResources() {
461 final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
462 if (dataConsumer != null) {
463 dataConsumer.releaseResources();
464 }
465 }
466
467 }
468
469 class BackendResponseHandler implements AsyncExecCallback {
470
471 private final HttpHost target;
472 private final HttpRequest request;
473 private final Instant requestDate;
474 private final Instant responseDate;
475 private final AsyncExecChain.Scope scope;
476 private final AsyncExecCallback asyncExecCallback;
477 private final AtomicReference<CachingAsyncDataConsumer> cachingConsumerRef;
478
479 BackendResponseHandler(
480 final HttpHost target,
481 final HttpRequest request,
482 final Instant requestDate,
483 final Instant responseDate,
484 final AsyncExecChain.Scope scope,
485 final AsyncExecCallback asyncExecCallback) {
486 this.target = target;
487 this.request = request;
488 this.requestDate = requestDate;
489 this.responseDate = responseDate;
490 this.scope = scope;
491 this.asyncExecCallback = asyncExecCallback;
492 this.cachingConsumerRef = new AtomicReference<>();
493 }
494
495 @Override
496 public AsyncDataConsumer handleResponse(
497 final HttpResponse backendResponse,
498 final EntityDetails entityDetails) throws HttpException, IOException {
499 final String exchangeId = scope.exchangeId;
500 responseCache.evictInvalidatedEntries(target, request, backendResponse, new FutureCallback<Boolean>() {
501
502 @Override
503 public void completed(final Boolean result) {
504 }
505
506 @Override
507 public void failed(final Exception ex) {
508 if (LOG.isDebugEnabled()) {
509 LOG.debug("{} unable to flush invalidated entries from cache", exchangeId, ex);
510 }
511 }
512
513 @Override
514 public void cancelled() {
515 }
516
517 });
518 if (isResponseTooBig(entityDetails)) {
519 if (LOG.isDebugEnabled()) {
520 LOG.debug("{} backend response is known to be too big", exchangeId);
521 }
522 return asyncExecCallback.handleResponse(backendResponse, entityDetails);
523 }
524
525 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
526 final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(backendResponse);
527 context.setResponseCacheControl(responseCacheControl);
528 final boolean cacheable = responseCachingPolicy.isResponseCacheable(responseCacheControl, request, backendResponse);
529 if (cacheable) {
530 storeRequestIfModifiedSinceFor304Response(request, backendResponse);
531 if (LOG.isDebugEnabled()) {
532 LOG.debug("{} caching backend response", exchangeId);
533 }
534 final CachingAsyncDataConsumer cachingDataConsumer = new CachingAsyncDataConsumer(
535 exchangeId, asyncExecCallback, backendResponse, entityDetails);
536 cachingConsumerRef.set(cachingDataConsumer);
537 return cachingDataConsumer;
538 }
539 if (LOG.isDebugEnabled()) {
540 LOG.debug("{} backend response is not cacheable", exchangeId);
541 }
542 return asyncExecCallback.handleResponse(backendResponse, entityDetails);
543 }
544
545 @Override
546 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
547 asyncExecCallback.handleInformationResponse(response);
548 }
549
550 void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
551 final String exchangeId = scope.exchangeId;
552 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
553 final CancellableDependency operation = scope.cancellableDependency;
554 operation.setDependency(responseCache.store(
555 target,
556 request,
557 backendResponse,
558 buffer,
559 requestDate,
560 responseDate,
561 new FutureCallback<CacheHit>() {
562
563 @Override
564 public void completed(final CacheHit hit) {
565 if (LOG.isDebugEnabled()) {
566 LOG.debug("{} backend response successfully cached", exchangeId);
567 }
568 try {
569 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
570 context.setCacheEntry(hit.entry);
571 triggerResponse(cacheResponse, scope, asyncExecCallback);
572 } catch (final ResourceIOException ex) {
573 asyncExecCallback.failed(ex);
574 }
575 }
576
577 @Override
578 public void failed(final Exception ex) {
579 asyncExecCallback.failed(ex);
580 }
581
582 @Override
583 public void cancelled() {
584 asyncExecCallback.failed(new InterruptedIOException());
585 }
586
587 }));
588
589 }
590
591 void triggerCachedResponse(final HttpCacheEntry entry) {
592 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
593 try {
594 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, entry);
595 context.setCacheEntry(entry);
596 triggerResponse(cacheResponse, scope, asyncExecCallback);
597 } catch (final ResourceIOException ex) {
598 asyncExecCallback.failed(ex);
599 }
600 }
601
602 @Override
603 public void completed() {
604 final String exchangeId = scope.exchangeId;
605 final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.getAndSet(null);
606 if (cachingDataConsumer == null || cachingDataConsumer.writtenThrough.get()) {
607 asyncExecCallback.completed();
608 return;
609 }
610 final HttpResponse backendResponse = cachingDataConsumer.backendResponse;
611 final ByteArrayBuffer buffer = cachingDataConsumer.bufferRef.getAndSet(null);
612
613
614 if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
615 responseCache.match(target, request, new FutureCallback<CacheMatch>() {
616
617 @Override
618 public void completed(final CacheMatch result) {
619 final CacheHit hit = result != null ? result.hit : null;
620 if (hit != null) {
621 if (LOG.isDebugEnabled()) {
622 LOG.debug("{} existing cache entry found, updating cache entry", exchangeId);
623 }
624 responseCache.update(
625 hit,
626 target,
627 request,
628 backendResponse,
629 requestDate,
630 responseDate,
631 new FutureCallback<CacheHit>() {
632
633 @Override
634 public void completed(final CacheHit updated) {
635 if (LOG.isDebugEnabled()) {
636 LOG.debug("{} cache entry updated, generating response from updated entry", exchangeId);
637 }
638 triggerCachedResponse(updated.entry);
639 }
640 @Override
641 public void failed(final Exception cause) {
642 if (LOG.isDebugEnabled()) {
643 LOG.debug("{} request failed: {}", exchangeId, cause.getMessage());
644 }
645 asyncExecCallback.failed(cause);
646 }
647
648 @Override
649 public void cancelled() {
650 if (LOG.isDebugEnabled()) {
651 LOG.debug("{} cache entry updated aborted", exchangeId);
652 }
653 asyncExecCallback.failed(new InterruptedIOException());
654 }
655
656 });
657 } else {
658 triggerNewCacheEntryResponse(backendResponse, responseDate, buffer);
659 }
660 }
661
662 @Override
663 public void failed(final Exception cause) {
664 asyncExecCallback.failed(cause);
665 }
666
667 @Override
668 public void cancelled() {
669 asyncExecCallback.failed(new InterruptedIOException());
670 }
671
672 });
673 } else {
674 if (cacheConfig.isFreshnessCheckEnabled()) {
675 final CancellableDependency operation = scope.cancellableDependency;
676 operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
677
678 @Override
679 public void completed(final CacheMatch result) {
680 final CacheHit hit = result != null ? result.hit : null;
681 if (HttpCacheEntry.isNewer(hit != null ? hit.entry : null, backendResponse)) {
682 if (LOG.isDebugEnabled()) {
683 LOG.debug("{} backend already contains fresher cache entry", exchangeId);
684 }
685 triggerCachedResponse(hit.entry);
686 } else {
687 triggerNewCacheEntryResponse(backendResponse, responseDate, buffer);
688 }
689 }
690
691 @Override
692 public void failed(final Exception cause) {
693 asyncExecCallback.failed(cause);
694 }
695
696 @Override
697 public void cancelled() {
698 asyncExecCallback.failed(new InterruptedIOException());
699 }
700
701 }));
702 } else {
703 triggerNewCacheEntryResponse(backendResponse, responseDate, buffer);
704 }
705 }
706 }
707
708 @Override
709 public void failed(final Exception cause) {
710 asyncExecCallback.failed(cause);
711 }
712
713 }
714
715 private void handleCacheHit(
716 final RequestCacheControl requestCacheControl,
717 final ResponseCacheControl responseCacheControl,
718 final CacheHit hit,
719 final HttpHost target,
720 final HttpRequest request,
721 final AsyncEntityProducer entityProducer,
722 final AsyncExecChain.Scope scope,
723 final AsyncExecChain chain,
724 final AsyncExecCallback asyncExecCallback) {
725 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
726 final String exchangeId = scope.exchangeId;
727
728 if (LOG.isDebugEnabled()) {
729 LOG.debug("{} cache hit: {} {}", exchangeId, request.getMethod(), request.getRequestUri());
730 }
731
732 context.setCacheResponseStatus(CacheResponseStatus.CACHE_HIT);
733 cacheHits.getAndIncrement();
734
735 final Instant now = getCurrentDate();
736
737 final CacheSuitability cacheSuitability = suitabilityChecker.assessSuitability(requestCacheControl, responseCacheControl, request, hit.entry, now);
738 if (LOG.isDebugEnabled()) {
739 LOG.debug("{} cache suitability: {}", exchangeId, cacheSuitability);
740 }
741 if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) {
742 if (LOG.isDebugEnabled()) {
743 LOG.debug("{} cache hit is fresh enough", exchangeId);
744 }
745 try {
746 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
747 context.setCacheEntry(hit.entry);
748 triggerResponse(cacheResponse, scope, asyncExecCallback);
749 } catch (final ResourceIOException ex) {
750 if (requestCacheControl.isOnlyIfCached()) {
751 if (LOG.isDebugEnabled()) {
752 LOG.debug("{} request marked only-if-cached", exchangeId);
753 }
754 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
755 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
756 triggerResponse(cacheResponse, scope, asyncExecCallback);
757 } else {
758 context.setCacheResponseStatus(CacheResponseStatus.FAILURE);
759 try {
760 chain.proceed(request, entityProducer, scope, asyncExecCallback);
761 } catch (final HttpException | IOException ex2) {
762 asyncExecCallback.failed(ex2);
763 }
764 }
765 }
766 } else {
767 if (requestCacheControl.isOnlyIfCached()) {
768 if (LOG.isDebugEnabled()) {
769 LOG.debug("{} cache entry not is not fresh and only-if-cached requested", exchangeId);
770 }
771 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
772 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
773 triggerResponse(cacheResponse, scope, asyncExecCallback);
774 } else if (cacheSuitability == CacheSuitability.MISMATCH) {
775 if (LOG.isDebugEnabled()) {
776 LOG.debug("{} cache entry does not match the request; calling backend", exchangeId);
777 }
778 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
779 } else if (entityProducer != null && !entityProducer.isRepeatable()) {
780 if (LOG.isDebugEnabled()) {
781 LOG.debug("{} request is not repeatable; calling backend", exchangeId);
782 }
783 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
784 } else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) {
785 if (LOG.isDebugEnabled()) {
786 LOG.debug("{} non-modified cache entry does not match the non-conditional request; calling backend", exchangeId);
787 }
788 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
789 } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) {
790 if (LOG.isDebugEnabled()) {
791 LOG.debug("{} revalidation required; revalidating cache entry", exchangeId);
792 }
793 revalidateCacheEntryWithoutFallback(responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
794 } else if (cacheSuitability == CacheSuitability.STALE_WHILE_REVALIDATED) {
795 if (cacheRevalidator != null) {
796 if (LOG.isDebugEnabled()) {
797 LOG.debug("{} serving stale with asynchronous revalidation", exchangeId);
798 }
799 try {
800 final String revalidationExchangeId = ExecSupport.getNextExchangeId();
801 context.setExchangeId(revalidationExchangeId);
802 final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
803 revalidationExchangeId,
804 scope.route,
805 scope.originalRequest,
806 new ComplexFuture<>(null),
807 HttpCacheContext.create(),
808 scope.execRuntime.fork(),
809 scope.scheduler,
810 scope.execCount);
811 if (LOG.isDebugEnabled()) {
812 LOG.debug("{} starting asynchronous revalidation exchange {}", exchangeId, revalidationExchangeId);
813 }
814 cacheRevalidator.revalidateCacheEntry(
815 hit.getEntryKey(),
816 asyncExecCallback,
817 c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c));
818 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
819 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
820 context.setCacheEntry(hit.entry);
821 triggerResponse(cacheResponse, scope, asyncExecCallback);
822 } catch (final IOException ex) {
823 asyncExecCallback.failed(ex);
824 }
825 } else {
826 if (LOG.isDebugEnabled()) {
827 LOG.debug("{} revalidating stale cache entry (asynchronous revalidation disabled)", exchangeId);
828 }
829 revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
830 }
831 } else if (cacheSuitability == CacheSuitability.STALE) {
832 if (LOG.isDebugEnabled()) {
833 LOG.debug("{} revalidating stale cache entry", exchangeId);
834 }
835 revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
836 } else {
837 if (LOG.isDebugEnabled()) {
838 LOG.debug("{} cache entry not usable; calling backend", exchangeId);
839 }
840 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
841 }
842 }
843 }
844
845 void revalidateCacheEntry(
846 final ResponseCacheControl responseCacheControl,
847 final CacheHit hit,
848 final HttpHost target,
849 final HttpRequest request,
850 final AsyncEntityProducer entityProducer,
851 final AsyncExecChain.Scope scope,
852 final AsyncExecChain chain,
853 final AsyncExecCallback asyncExecCallback) {
854 final Instant requestDate = getCurrentDate();
855 final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
856 responseCacheControl,
857 BasicRequestBuilder.copy(request).build(),
858 hit.entry);
859 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
860 chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
861
862 final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
863
864 void triggerUpdatedCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate) {
865 final CancellableDependency operation = scope.cancellableDependency;
866 operation.setDependency(responseCache.update(
867 hit,
868 target,
869 request,
870 backendResponse,
871 requestDate,
872 responseDate,
873 new FutureCallback<CacheHit>() {
874
875 @Override
876 public void completed(final CacheHit updated) {
877 try {
878 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, updated.entry, responseDate);
879 context.setCacheEntry(updated.entry);
880 triggerResponse(cacheResponse, scope, asyncExecCallback);
881 } catch (final ResourceIOException ex) {
882 asyncExecCallback.failed(ex);
883 }
884 }
885
886 @Override
887 public void failed(final Exception ex) {
888 asyncExecCallback.failed(ex);
889 }
890
891 @Override
892 public void cancelled() {
893 asyncExecCallback.failed(new InterruptedIOException());
894 }
895
896 }));
897 }
898
899 AsyncExecCallback evaluateResponse(final HttpResponse backendResponse, final Instant responseDate) {
900 final int statusCode = backendResponse.getCode();
901 if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
902 context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
903 cacheUpdates.getAndIncrement();
904 }
905 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
906 return new AsyncExecCallbackWrapper(() -> triggerUpdatedCacheEntryResponse(backendResponse, responseDate), asyncExecCallback::failed);
907 }
908 return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
909 }
910
911 @Override
912 public AsyncDataConsumer handleResponse(
913 final HttpResponse backendResponse1,
914 final EntityDetails entityDetails) throws HttpException, IOException {
915
916 final Instant responseDate = getCurrentDate();
917
918 final AsyncExecCallback callback1;
919 if (HttpCacheEntry.isNewer(hit.entry, backendResponse1)) {
920
921 final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
922 BasicRequestBuilder.copy(scope.originalRequest).build());
923
924 callback1 = new AsyncExecCallbackWrapper(() -> chainProceed(unconditional, entityProducer, scope, chain, new AsyncExecCallback() {
925
926 @Override
927 public AsyncDataConsumer handleResponse(
928 final HttpResponse backendResponse2,
929 final EntityDetails entityDetails1) throws HttpException, IOException {
930 final Instant responseDate2 = getCurrentDate();
931 final AsyncExecCallback callback2 = evaluateResponse(backendResponse2, responseDate2);
932 callbackRef.set(callback2);
933 return callback2.handleResponse(backendResponse2, entityDetails1);
934 }
935
936 @Override
937 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
938 final AsyncExecCallback callback2 = callbackRef.getAndSet(null);
939 if (callback2 != null) {
940 callback2.handleInformationResponse(response);
941 } else {
942 asyncExecCallback.handleInformationResponse(response);
943 }
944 }
945
946 @Override
947 public void completed() {
948 final AsyncExecCallback callback2 = callbackRef.getAndSet(null);
949 if (callback2 != null) {
950 callback2.completed();
951 } else {
952 asyncExecCallback.completed();
953 }
954 }
955
956 @Override
957 public void failed(final Exception cause) {
958 final AsyncExecCallback callback2 = callbackRef.getAndSet(null);
959 if (callback2 != null) {
960 callback2.failed(cause);
961 } else {
962 asyncExecCallback.failed(cause);
963 }
964 }
965
966 }), asyncExecCallback::failed);
967 } else {
968 callback1 = evaluateResponse(backendResponse1, responseDate);
969 }
970 callbackRef.set(callback1);
971 return callback1.handleResponse(backendResponse1, entityDetails);
972 }
973
974 @Override
975 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
976 final AsyncExecCallback callback1 = callbackRef.getAndSet(null);
977 if (callback1 != null) {
978 callback1.handleInformationResponse(response);
979 } else {
980 asyncExecCallback.handleInformationResponse(response);
981 }
982 }
983
984 @Override
985 public void completed() {
986 final AsyncExecCallback callback1 = callbackRef.getAndSet(null);
987 if (callback1 != null) {
988 callback1.completed();
989 } else {
990 asyncExecCallback.completed();
991 }
992 }
993
994 @Override
995 public void failed(final Exception cause) {
996 final AsyncExecCallback callback1 = callbackRef.getAndSet(null);
997 if (callback1 != null) {
998 callback1.failed(cause);
999 } else {
1000 asyncExecCallback.failed(cause);
1001 }
1002 }
1003
1004 });
1005
1006 }
1007
1008 void revalidateCacheEntryWithoutFallback(
1009 final ResponseCacheControl responseCacheControl,
1010 final CacheHit hit,
1011 final HttpHost target,
1012 final HttpRequest request,
1013 final AsyncEntityProducer entityProducer,
1014 final AsyncExecChain.Scope scope,
1015 final AsyncExecChain chain,
1016 final AsyncExecCallback asyncExecCallback) {
1017 final String exchangeId = scope.exchangeId;
1018 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
1019 revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() {
1020
1021 private final AtomicBoolean committed = new AtomicBoolean();
1022
1023 @Override
1024 public AsyncDataConsumer handleResponse(final HttpResponse response,
1025 final EntityDetails entityDetails) throws HttpException, IOException {
1026 committed.set(true);
1027 return asyncExecCallback.handleResponse(response, entityDetails);
1028 }
1029
1030 @Override
1031 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
1032 asyncExecCallback.handleInformationResponse(response);
1033 }
1034
1035 @Override
1036 public void completed() {
1037 asyncExecCallback.completed();
1038 }
1039
1040 @Override
1041 public void failed(final Exception cause) {
1042 if (!committed.get() && cause instanceof IOException) {
1043 if (LOG.isDebugEnabled()) {
1044 LOG.debug("{} I/O error while revalidating cache entry", exchangeId, cause);
1045 }
1046 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
1047 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
1048 triggerResponse(cacheResponse, scope, asyncExecCallback);
1049 } else {
1050 asyncExecCallback.failed(cause);
1051 }
1052 }
1053
1054 });
1055 }
1056
1057 void revalidateCacheEntryWithFallback(
1058 final RequestCacheControl requestCacheControl,
1059 final ResponseCacheControl responseCacheControl,
1060 final CacheHit hit,
1061 final HttpHost target,
1062 final HttpRequest request,
1063 final AsyncEntityProducer entityProducer,
1064 final AsyncExecChain.Scope scope,
1065 final AsyncExecChain chain,
1066 final AsyncExecCallback asyncExecCallback) {
1067 final String exchangeId = scope.exchangeId;
1068 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
1069 revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, scope, chain, new AsyncExecCallback() {
1070
1071 private final AtomicReference<HttpResponse> committed = new AtomicReference<>();
1072
1073 @Override
1074 public AsyncDataConsumer handleResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
1075 final int status = response.getCode();
1076 if (staleIfErrorAppliesTo(status) &&
1077 suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) {
1078 if (LOG.isDebugEnabled()) {
1079 LOG.debug("{} serving stale response due to {} status and stale-if-error enabled", exchangeId, status);
1080 }
1081 return null;
1082 }
1083 committed.set(response);
1084 return asyncExecCallback.handleResponse(response, entityDetails);
1085 }
1086
1087 @Override
1088 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
1089 asyncExecCallback.handleInformationResponse(response);
1090 }
1091
1092 @Override
1093 public void completed() {
1094 final HttpResponse response = committed.get();
1095 if (response == null) {
1096 try {
1097 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
1098 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
1099 context.setCacheEntry(hit.entry);
1100 triggerResponse(cacheResponse, scope, asyncExecCallback);
1101 } catch (final IOException ex) {
1102 asyncExecCallback.failed(ex);
1103 }
1104 } else {
1105 asyncExecCallback.completed();
1106 }
1107 }
1108
1109 @Override
1110 public void failed(final Exception cause) {
1111 final HttpResponse response = committed.get();
1112 if (response == null) {
1113 if (LOG.isDebugEnabled()) {
1114 LOG.debug("{} I/O error while revalidating cache entry", exchangeId, cause);
1115 }
1116 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
1117 if (cause instanceof IOException &&
1118 suitabilityChecker.isSuitableIfError(requestCacheControl, responseCacheControl, hit.entry, getCurrentDate())) {
1119 if (LOG.isDebugEnabled()) {
1120 LOG.debug("{} serving stale response due to IOException and stale-if-error enabled", exchangeId);
1121 }
1122 try {
1123 final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
1124 context.setCacheEntry(hit.entry);
1125 triggerResponse(cacheResponse, scope, asyncExecCallback);
1126 } catch (final IOException ex) {
1127 asyncExecCallback.failed(cause);
1128 }
1129 } else {
1130 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
1131 triggerResponse(cacheResponse, scope, asyncExecCallback);
1132 }
1133 } else {
1134 asyncExecCallback.failed(cause);
1135 }
1136 }
1137
1138 });
1139 }
1140 private void handleCacheMiss(
1141 final RequestCacheControl requestCacheControl,
1142 final CacheHit partialMatch,
1143 final HttpHost target,
1144 final HttpRequest request,
1145 final AsyncEntityProducer entityProducer,
1146 final AsyncExecChain.Scope scope,
1147 final AsyncExecChain chain,
1148 final AsyncExecCallback asyncExecCallback) {
1149 final String exchangeId = scope.exchangeId;
1150
1151 if (LOG.isDebugEnabled()) {
1152 LOG.debug("{} cache miss: {} {}", exchangeId, request.getMethod(), request.getRequestUri());
1153 }
1154 cacheMisses.getAndIncrement();
1155
1156 final CancellableDependency operation = scope.cancellableDependency;
1157 if (requestCacheControl.isOnlyIfCached()) {
1158 if (LOG.isDebugEnabled()) {
1159 LOG.debug("{} request marked only-if-cached", exchangeId);
1160 }
1161 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
1162 context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
1163 final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
1164 triggerResponse(cacheResponse, scope, asyncExecCallback);
1165 }
1166
1167 if (partialMatch != null && partialMatch.entry.hasVariants() && entityProducer == null) {
1168 operation.setDependency(responseCache.getVariants(
1169 partialMatch,
1170 new FutureCallback<Collection<CacheHit>>() {
1171
1172 @Override
1173 public void completed(final Collection<CacheHit> variants) {
1174 if (variants != null && !variants.isEmpty()) {
1175 negotiateResponseFromVariants(target, request, entityProducer, scope, chain, asyncExecCallback, variants);
1176 } else {
1177 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
1178 }
1179 }
1180
1181 @Override
1182 public void failed(final Exception ex) {
1183 asyncExecCallback.failed(ex);
1184 }
1185
1186 @Override
1187 public void cancelled() {
1188 asyncExecCallback.failed(new InterruptedIOException());
1189 }
1190
1191 }));
1192 } else {
1193 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
1194 }
1195 }
1196
1197 void negotiateResponseFromVariants(
1198 final HttpHost target,
1199 final HttpRequest request,
1200 final AsyncEntityProducer entityProducer,
1201 final AsyncExecChain.Scope scope,
1202 final AsyncExecChain chain,
1203 final AsyncExecCallback asyncExecCallback,
1204 final Collection<CacheHit> variants) {
1205 final String exchangeId = scope.exchangeId;
1206 final CancellableDependency operation = scope.cancellableDependency;
1207 final Map<ETag, CacheHit> variantMap = new HashMap<>();
1208 for (final CacheHit variant : variants) {
1209 final ETag eTag = variant.entry.getETag();
1210 if (eTag != null) {
1211 variantMap.put(eTag, variant);
1212 }
1213 }
1214
1215 final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
1216 request,
1217 variantMap.keySet());
1218
1219 final Instant requestDate = getCurrentDate();
1220 chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
1221
1222 final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
1223
1224 void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final CacheHit match) {
1225 final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
1226 context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
1227 cacheUpdates.getAndIncrement();
1228
1229 operation.setDependency(responseCache.storeFromNegotiated(
1230 match,
1231 target,
1232 request,
1233 backendResponse,
1234 requestDate,
1235 responseDate,
1236 new FutureCallback<CacheHit>() {
1237
1238 @Override
1239 public void completed(final CacheHit hit) {
1240 try {
1241 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
1242 context.setCacheEntry(hit.entry);
1243 triggerResponse(cacheResponse, scope, asyncExecCallback);
1244 } catch (final ResourceIOException ex) {
1245 asyncExecCallback.failed(ex);
1246 }
1247 }
1248
1249 @Override
1250 public void failed(final Exception ex) {
1251 asyncExecCallback.failed(ex);
1252 }
1253
1254 @Override
1255 public void cancelled() {
1256 asyncExecCallback.failed(new InterruptedIOException());
1257 }
1258
1259 }));
1260 }
1261
1262 @Override
1263 public AsyncDataConsumer handleResponse(
1264 final HttpResponse backendResponse,
1265 final EntityDetails entityDetails) throws HttpException, IOException {
1266 final Instant responseDate = getCurrentDate();
1267 final AsyncExecCallback callback;
1268 if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
1269 callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
1270 } else {
1271 final ETag resultEtag = ETag.get(backendResponse);
1272 if (resultEtag == null) {
1273 if (LOG.isDebugEnabled()) {
1274 LOG.debug("{} 304 response did not contain ETag", exchangeId);
1275 }
1276 callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
1277 } else {
1278 final CacheHit match = variantMap.get(resultEtag);
1279 if (match == null) {
1280 if (LOG.isDebugEnabled()) {
1281 LOG.debug("{} 304 response did not contain ETag matching one sent in If-None-Match", exchangeId);
1282 }
1283 callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
1284 } else {
1285 if (HttpCacheEntry.isNewer(match.entry, backendResponse)) {
1286 final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
1287 BasicRequestBuilder.copy(request).build());
1288 callback = new AsyncExecCallbackWrapper(() -> callBackend(target, unconditional, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
1289 } else {
1290 callback = new AsyncExecCallbackWrapper(() -> updateVariantCacheEntry(backendResponse, responseDate, match), asyncExecCallback::failed);
1291 }
1292 }
1293 }
1294 }
1295 callbackRef.set(callback);
1296 return callback.handleResponse(backendResponse, entityDetails);
1297 }
1298
1299 @Override
1300 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
1301 final AsyncExecCallback callback = callbackRef.getAndSet(null);
1302 if (callback != null) {
1303 callback.handleInformationResponse(response);
1304 } else {
1305 asyncExecCallback.handleInformationResponse(response);
1306 }
1307 }
1308
1309 @Override
1310 public void completed() {
1311 final AsyncExecCallback callback = callbackRef.getAndSet(null);
1312 if (callback != null) {
1313 callback.completed();
1314 } else {
1315 asyncExecCallback.completed();
1316 }
1317 }
1318
1319 @Override
1320 public void failed(final Exception cause) {
1321 final AsyncExecCallback callback = callbackRef.getAndSet(null);
1322 if (callback != null) {
1323 callback.failed(cause);
1324 } else {
1325 asyncExecCallback.failed(cause);
1326 }
1327 }
1328
1329 });
1330
1331 }
1332
1333 }