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 HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
        final String exchangeId = scope.exchangeId;

        if (LOG.isDebugEnabled()) {
            LOG.debug("{} cache hit: {} {}", exchangeId, request.getMethod(), request.getRequestUri());
        }

        context.setCacheResponseStatus(CacheResponseStatus.CACHE_HIT);
        cacheHits.getAndIncrement();

        final Instant now = getCurrentDate();

        final CacheSuitability cacheSuitability = suitabilityChecker.assessSuitability(requestCacheControl, responseCacheControl, request, hit.entry, now);
        if (LOG.isDebugEnabled()) {
            LOG.debug("{} cache suitability: {}", exchangeId, cacheSuitability);
        }
        if (cacheSuitability == CacheSuitability.FRESH || cacheSuitability == CacheSuitability.FRESH_ENOUGH) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("{} cache hit is fresh enough", exchangeId);
            }
            try {
                final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, now);
                context.setCacheEntry(hit.entry);
                triggerResponse(cacheResponse, scope, asyncExecCallback);
            } catch (final ResourceIOException ex) {
                if (requestCacheControl.isOnlyIfCached()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} request marked only-if-cached", exchangeId);
                    }
                    context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                    final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
                    triggerResponse(cacheResponse, scope, asyncExecCallback);
                } else {
                    context.setCacheResponseStatus(CacheResponseStatus.FAILURE);
                    try {
                        chain.proceed(request, entityProducer, scope, asyncExecCallback);
                    } catch (final HttpException | IOException ex2) {
                        asyncExecCallback.failed(ex2);
                    }
                }
            }
        } else {
            if (requestCacheControl.isOnlyIfCached()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} cache entry not is not fresh and only-if-cached requested", exchangeId);
                }
                context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                final SimpleHttpResponse cacheResponse = generateGatewayTimeout();
                triggerResponse(cacheResponse, scope, asyncExecCallback);
            } else if (cacheSuitability == CacheSuitability.MISMATCH) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} cache entry does not match the request; calling backend", exchangeId);
                }
                callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
            } else if (entityProducer != null && !entityProducer.isRepeatable()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} request is not repeatable; calling backend", exchangeId);
                }
                callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
            } else if (hit.entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} non-modified cache entry does not match the non-conditional request; calling backend", exchangeId);
                }
                callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
            } else if (cacheSuitability == CacheSuitability.REVALIDATION_REQUIRED) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} revalidation required; revalidating cache entry", exchangeId);
                }
                revalidateCacheEntryWithoutFallback(responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
            } else if (cacheSuitability == CacheSuitability.STALE_WHILE_REVALIDATED) {
                if (cacheRevalidator != null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} serving stale with asynchronous revalidation", exchangeId);
                    }
                    try {
                        final String revalidationExchangeId = ExecSupport.getNextExchangeId();
                        context.setExchangeId(revalidationExchangeId);
                        final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
                                revalidationExchangeId,
                                scope.route,
                                scope.originalRequest,
                                new ComplexFuture<>(null),
                                HttpCacheContext.create(),
                                scope.execRuntime.fork(),
                                scope.scheduler,
                                scope.execCount);
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("{} starting asynchronous revalidation exchange {}", exchangeId, revalidationExchangeId);
                        }
                        cacheRevalidator.revalidateCacheEntry(
                                hit.getEntryKey(),
                                asyncExecCallback,
                                c -> revalidateCacheEntry(responseCacheControl, hit, target, request, entityProducer, fork, chain, c));
                        context.setCacheResponseStatus(CacheResponseStatus.CACHE_MODULE_RESPONSE);
                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
                        context.setCacheEntry(hit.entry);
                        triggerResponse(cacheResponse, scope, asyncExecCallback);
                    } catch (final IOException ex) {
                        asyncExecCallback.failed(ex);
                    }
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("{} revalidating stale cache entry (asynchronous revalidation disabled)", exchangeId);
                    }
                    revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
                }
            } else if (cacheSuitability == CacheSuitability.STALE) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} revalidating stale cache entry", exchangeId);
                }
                revalidateCacheEntryWithFallback(requestCacheControl, responseCacheControl, hit, target, request, entityProducer, scope, chain, asyncExecCallback);
            } else {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("{} cache entry not usable; calling backend", exchangeId);
                }
                callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
            }
        }
    }
Request executor in the request execution chain that is responsible for
transparent client-side caching.
<p>
The current implementation is conditionally
compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed),
although quite a lot, though not all, of the SHOULDs and SHOULD NOTs
are obeyed too.

@since 5.0
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java