You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2017/10/16 12:53:48 UTC
[2/6] httpcomponents-client git commit: Asynchronous cache exec
interceptor;
removed incomplete response checks as response integrity is enforced in the
transport layer;
async cache re-validation is currently broken in the classic and unsuppoted in
the a
Asynchronous cache exec interceptor; removed incomplete response checks as response integrity is enforced in the transport layer; async cache re-validation is currently broken in the classic and unsuppoted in the async implementations
Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/3ba71acb
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/3ba71acb
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/3ba71acb
Branch: refs/heads/master
Commit: 3ba71acb6b7e493b7594aa4e5c49ebd4ef03913a
Parents: 6076f55
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sun Oct 8 13:27:21 2017 +0200
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Oct 16 13:53:07 2017 +0200
----------------------------------------------------------------------
.../http/impl/cache/AsyncCachingExec.java | 695 +++++++++++++++++++
.../hc/client5/http/impl/cache/CachingExec.java | 530 +-------------
.../http/impl/cache/CachingExecBase.java | 453 ++++++++++++
.../impl/cache/CachingHttpClientBuilder.java | 16 +-
.../http/impl/cache/HeapResourceFactory.java | 2 +
.../http/impl/cache/TestCachingExec.java | 11 +-
.../http/impl/cache/TestCachingExecChain.java | 75 +-
.../impl/cache/TestProtocolRequirements.java | 32 -
.../http/impl/cache/TestRFC5861Compliance.java | 15 +-
9 files changed, 1204 insertions(+), 625 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3ba71acb/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
new file mode 100644
index 0000000..104ca75
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -0,0 +1,695 @@
+/*
+ * ====================================================================
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+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.HeaderConstants;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.HttpCacheStorage;
+import org.apache.hc.client5.http.cache.ResourceFactory;
+import org.apache.hc.client5.http.cache.ResourceIOException;
+import org.apache.hc.client5.http.impl.RequestCopier;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+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.impl.BasicEntityDetails;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.ByteArrayBuffer;
+
+/**
+ * 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
+ */
+@Contract(threading = ThreadingBehavior.SAFE) // So long as the responseCache implementation is threadsafe
+public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler {
+
+ private final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder;
+ public AsyncCachingExec(
+ final HttpCache cache,
+ final CacheConfig config) {
+ super(cache, config);
+ this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(RequestCopier.INSTANCE);
+ }
+
+ public AsyncCachingExec(
+ final ResourceFactory resourceFactory,
+ final HttpCacheStorage storage,
+ final CacheConfig config) {
+ this(new BasicHttpCache(resourceFactory, storage), config);
+ }
+
+ public AsyncCachingExec() {
+ this(new BasicHttpCache(), CacheConfig.DEFAULT);
+ }
+
+ AsyncCachingExec(
+ final HttpCache responseCache,
+ final CacheValidityPolicy validityPolicy,
+ final ResponseCachingPolicy responseCachingPolicy,
+ final CachedHttpResponseGenerator responseGenerator,
+ final CacheableRequestPolicy cacheableRequestPolicy,
+ final CachedResponseSuitabilityChecker suitabilityChecker,
+ final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder,
+ final ResponseProtocolCompliance responseCompliance,
+ final RequestProtocolCompliance requestCompliance,
+ final CacheConfig config) {
+ super(responseCache, validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
+ suitabilityChecker, responseCompliance, requestCompliance, config);
+ this.conditionalRequestBuilder = conditionalRequestBuilder;
+ }
+
+ private void triggerResponse(
+ final SimpleHttpResponse cacheResponse,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecCallback asyncExecCallback) {
+ scope.clientContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, cacheResponse);
+ scope.execRuntime.releaseConnection();
+
+ final SimpleBody body = cacheResponse.getBody();
+ final byte[] content = body != null ? body.getBodyBytes() : null;
+ final ContentType contentType = body != null ? body.getContentType() : null;
+ try {
+ final AsyncDataConsumer dataConsumer = asyncExecCallback.handleResponse(
+ cacheResponse,
+ content != null ? new BasicEntityDetails(content.length, contentType) : null);
+ if (dataConsumer != null) {
+ dataConsumer.consume(ByteBuffer.wrap(content));
+ dataConsumer.streamEnd(null);
+ }
+ } catch (final HttpException | IOException ex) {
+ asyncExecCallback.failed(ex);
+ }
+ }
+
+ @Override
+ public void execute(
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
+ Args.notNull(request, "HTTP request");
+ Args.notNull(scope, "Scope");
+
+ final HttpRoute route = scope.route;
+ final HttpClientContext context = scope.clientContext;
+ context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
+ context.setAttribute(HttpClientContext.HTTP_REQUEST, request);
+
+ final URIAuthority authority = request.getAuthority();
+ final String scheme = request.getScheme();
+ final HttpHost target = authority != null ? new HttpHost(authority, scheme) : route.getTargetHost();;
+ final String via = generateViaHeader(request);
+
+ // default response context
+ setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
+
+ if (clientRequestsOurOptions(request)) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ triggerResponse(SimpleHttpResponse.create(HttpStatus.SC_NOT_IMPLEMENTED), scope, asyncExecCallback);
+ return;
+ }
+
+ final SimpleHttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
+ if (fatalErrorResponse != null) {
+ triggerResponse(fatalErrorResponse, scope, asyncExecCallback);
+ return;
+ }
+
+ requestCompliance.makeRequestCompliant(request);
+ request.addHeader("Via",via);
+
+ if (!cacheableRequestPolicy.isServableFromCache(request)) {
+ log.debug("Request is not servable from cache");
+ flushEntriesInvalidatedByRequest(target, request);
+ callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+ } else {
+ final HttpCacheEntry entry = satisfyFromCache(target, request);
+ if (entry == null) {
+ log.debug("Cache miss");
+ handleCacheMiss(target, request, entityProducer, scope, chain, asyncExecCallback);
+ } else {
+ handleCacheHit(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+ }
+ }
+ }
+
+ interface InternalCallback extends AsyncExecCallback {
+
+ boolean cacheResponse(HttpResponse backendResponse) throws HttpException, IOException;
+
+ }
+
+ void callBackend(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
+ callBackendInternal(target, request, entityProducer, scope, chain, new InternalCallback() {
+
+ @Override
+ public boolean cacheResponse(final HttpResponse backendResponse) {
+ return true;
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+ return asyncExecCallback.handleResponse(response, entityDetails);
+ }
+
+ @Override
+ public void completed() {
+ asyncExecCallback.completed();
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ asyncExecCallback.failed(cause);
+ }
+
+ });
+ }
+
+ void callBackendInternal(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final InternalCallback asyncExecCallback) throws HttpException, IOException {
+ log.trace("Calling the backend");
+ final Date requestDate = getCurrentDate();
+ chain.proceed(request, entityProducer, scope, new AsyncExecCallback() {
+
+ private final AtomicReference<ByteArrayBuffer> bufferRef = new AtomicReference<>();
+ private final AtomicReference<AsyncDataConsumer> dataConsumerRef = new AtomicReference<>();
+ private final AtomicReference<SimpleHttpResponse> responseRef = new AtomicReference<>();
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse backendResponse,
+ final EntityDetails entityDetails) throws HttpException, IOException {
+ final Date responseDate = getCurrentDate();
+ backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+
+ log.trace("Handling Backend response");
+ responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
+
+ final boolean cacheable = asyncExecCallback.cacheResponse(backendResponse)
+ && responseCachingPolicy.isResponseCacheable(request, backendResponse);
+ responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
+ if (cacheable) {
+ if (!alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
+ storeRequestIfModifiedSinceFor304Response(request, backendResponse);
+ bufferRef.set(new ByteArrayBuffer(1024));
+ }
+ } else {
+ try {
+ responseCache.flushCacheEntriesFor(target, request);
+ } catch (final IOException ioe) {
+ log.warn("Unable to flush invalid cache entries", ioe);
+ }
+ }
+ if (bufferRef.get() != null) {
+ if (entityDetails == null) {
+ scope.execRuntime.releaseConnection();
+ final HttpCacheEntry entry = responseCache.createCacheEntry(
+ target, request, backendResponse, null, requestDate, responseDate);
+ responseRef.set(responseGenerator.generateResponse(request, entry));
+ return null;
+ } else {
+ return new AsyncDataConsumer() {
+
+ @Override
+ public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+ final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
+ if (dataConsumer != null) {
+ dataConsumer.updateCapacity(capacityChannel);
+ } else {
+ capacityChannel.update(Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public final int consume(final ByteBuffer src) throws IOException {
+ final ByteArrayBuffer buffer = bufferRef.get();
+ if (buffer != null) {
+ if (src.hasArray()) {
+ buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
+ } else {
+ while (src.hasRemaining()) {
+ buffer.append(src.get());
+ }
+ }
+ if (buffer.length() > cacheConfig.getMaxObjectSize()) {
+ // Over the max limit. Stop buffering and forward the response
+ // along with all the data buffered so far to the caller.
+ bufferRef.set(null);
+ try {
+ final AsyncDataConsumer dataConsumer = asyncExecCallback.handleResponse(
+ backendResponse, entityDetails);
+ if (dataConsumer != null) {
+ dataConsumerRef.set(dataConsumer);
+ return dataConsumer.consume(ByteBuffer.wrap(buffer.array(), 0, buffer.length()));
+ }
+ } catch (final HttpException ex) {
+ asyncExecCallback.failed(ex);
+ }
+ }
+ return Integer.MAX_VALUE;
+ } else {
+ final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
+ if (dataConsumer != null) {
+ return dataConsumer.consume(src);
+ } else {
+ return Integer.MAX_VALUE;
+ }
+ }
+ }
+
+ @Override
+ public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+ scope.execRuntime.releaseConnection();
+ final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
+ if (dataConsumer != null) {
+ dataConsumer.streamEnd(trailers);
+ }
+ final ByteArrayBuffer buffer = bufferRef.getAndSet(null);
+ if (buffer != null) {
+ final HttpCacheEntry entry = responseCache.createCacheEntry(
+ target, request, backendResponse, buffer, requestDate, responseDate);
+ responseRef.set(responseGenerator.generateResponse(request, entry));
+ }
+ }
+
+ @Override
+ public void releaseResources() {
+ final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
+ if (dataConsumer != null) {
+ dataConsumer.releaseResources();
+ }
+ }
+
+ };
+ }
+ } else {
+ return asyncExecCallback.handleResponse(backendResponse, entityDetails);
+ }
+ }
+
+ @Override
+ public void completed() {
+ final SimpleHttpResponse response = responseRef.getAndSet(null);
+ if (response != null) {
+ triggerResponse(response, scope, asyncExecCallback);
+ } else {
+ asyncExecCallback.completed();
+ }
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ try {
+ scope.execRuntime.discardConnection();
+ } finally {
+ asyncExecCallback.failed(cause);
+ }
+ }
+
+ });
+ }
+
+ private void handleCacheHit(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback,
+ final HttpCacheEntry entry) throws IOException, HttpException {
+ final HttpClientContext context = scope.clientContext;
+ recordCacheHit(target, request);
+ final Date now = getCurrentDate();
+ if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
+ log.debug("Cache hit");
+ try {
+ final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ } catch (final ResourceIOException ex) {
+ recordCacheFailure(target, request);
+ if (!mayCallBackend(request)) {
+ final SimpleHttpResponse cacheResponse = generateGatewayTimeout(context);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ } else {
+ setResponseStatus(scope.clientContext, CacheResponseStatus.FAILURE);
+ chain.proceed(request, entityProducer, scope, asyncExecCallback);
+ }
+ }
+ } else if (!mayCallBackend(request)) {
+ log.debug("Cache entry not suitable but only-if-cached requested");
+ final SimpleHttpResponse cacheResponse = generateGatewayTimeout(context);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
+ log.debug("Revalidating cache entry");
+ revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+ } else {
+ log.debug("Cache entry not usable; calling backend");
+ callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+ }
+ }
+
+ void revalidateCacheEntry(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback,
+ final HttpCacheEntry cacheEntry) throws IOException, HttpException {
+
+ final Date requestDate = getCurrentDate();
+ final InternalCallback internalCallback = new InternalCallback() {
+
+ private final AtomicReference<Date> responseDateRef = new AtomicReference<>(null);
+ private final AtomicReference<HttpResponse> backendResponseRef = new AtomicReference<>(null);
+
+ @Override
+ public boolean cacheResponse(final HttpResponse backendResponse) throws IOException {
+ final Date responseDate = getCurrentDate();
+ responseDateRef.set(requestDate);
+ final int statusCode = backendResponse.getCode();
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
+ recordCacheUpdate(scope.clientContext);
+ }
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
+ backendResponseRef.set(backendResponse);
+ return false;
+ }
+ if (staleIfErrorAppliesTo(statusCode)
+ && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
+ && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
+ backendResponseRef.set(backendResponse);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+ if (backendResponseRef.get() == null) {
+ return asyncExecCallback.handleResponse(response, entityDetails);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void completed() {
+ final HttpResponse backendResponse = backendResponseRef.getAndSet(null);
+ if (backendResponse != null) {
+ final int statusCode = backendResponse.getCode();
+ try {
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
+ final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
+ target, request, cacheEntry, backendResponse, requestDate, responseDateRef.get());
+ if (suitabilityChecker.isConditional(request)
+ && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updatedEntry);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ } else {
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ }
+ } else if (staleIfErrorAppliesTo(statusCode)) {
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, cacheEntry);
+ cacheResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ }
+ } catch (final IOException ex) {
+ asyncExecCallback.failed(ex);
+ }
+ } else {
+ asyncExecCallback.completed();
+ }
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ asyncExecCallback.failed(cause);
+ }
+
+ };
+
+ final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
+ scope.originalRequest, cacheEntry);
+ callBackendInternal(target, conditionalRequest, entityProducer, scope, chain, new InternalCallback() {
+
+ private final AtomicBoolean revalidate = new AtomicBoolean(false);
+
+ @Override
+ public boolean cacheResponse(final HttpResponse backendResponse) throws HttpException, IOException {
+ if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
+ revalidate.set(true);
+ return false;
+ } else {
+ return internalCallback.cacheResponse(backendResponse);
+ }
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse response,
+ final EntityDetails entityDetails) throws HttpException, IOException {
+ if (revalidate.get()) {
+ return null;
+ } else {
+ return internalCallback.handleResponse(response, entityDetails);
+ }
+ }
+
+ @Override
+ public void completed() {
+ if (revalidate.getAndSet(false)) {
+ final HttpRequest unconditionalRequest = conditionalRequestBuilder.buildUnconditionalRequest(scope.originalRequest);
+ try {
+ callBackendInternal(target, unconditionalRequest, entityProducer, scope, chain, new InternalCallback() {
+
+ @Override
+ public boolean cacheResponse(final HttpResponse backendResponse) throws HttpException, IOException {
+ return internalCallback.cacheResponse(backendResponse);
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+ return internalCallback.handleResponse(response, entityDetails);
+ }
+
+ @Override
+ public void completed() {
+ internalCallback.completed();
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ internalCallback.failed(cause);
+ }
+
+ });
+ } catch (final HttpException | IOException ex) {
+ internalCallback.failed(ex);
+ }
+ } else {
+ internalCallback.completed();
+ }
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ internalCallback.failed(cause);
+ }
+
+ });
+
+ }
+
+ private void handleCacheMiss(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback) throws IOException, HttpException {
+ recordCacheMiss(target, request);
+
+ if (!mayCallBackend(request)) {
+ final SimpleHttpResponse cacheResponse = SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ return;
+ }
+
+ final Map<String, Variant> variants = getExistingCacheVariants(target, request);
+ if (variants != null && !variants.isEmpty()) {
+ negotiateResponseFromVariants(target, request, entityProducer, scope, chain, asyncExecCallback, variants);
+ } else {
+ callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+ }
+ }
+
+ void negotiateResponseFromVariants(
+ final HttpHost target,
+ final HttpRequest request,
+ final AsyncEntityProducer entityProducer,
+ final AsyncExecChain.Scope scope,
+ final AsyncExecChain chain,
+ final AsyncExecCallback asyncExecCallback,
+ final Map<String, Variant> variants) throws IOException, HttpException {
+ final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
+ request, variants);
+
+ final Date requestDate = getCurrentDate();
+ callBackendInternal(target, conditionalRequest, entityProducer, scope, chain, new InternalCallback() {
+
+ private final AtomicReference<Date> responseDateRef = new AtomicReference<>(null);
+ private final AtomicReference<HttpResponse> backendResponseRef = new AtomicReference<>(null);
+
+ @Override
+ public boolean cacheResponse(final HttpResponse backendResponse) throws IOException {
+ responseDateRef.set(getCurrentDate());
+ if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
+ backendResponseRef.set(backendResponse);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public AsyncDataConsumer handleResponse(
+ final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+ return asyncExecCallback.handleResponse(response, entityDetails);
+ }
+
+ @Override
+ public void completed() {
+ final HttpResponse backendResponse = backendResponseRef.getAndSet(null);
+ if (backendResponse != null) {
+ try {
+ final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG);
+ if (resultEtagHeader == null) {
+ log.warn("304 response did not contain ETag");
+ callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+ return;
+ }
+ final String resultEtag = resultEtagHeader.getValue();
+ final Variant matchingVariant = variants.get(resultEtag);
+ if (matchingVariant == null) {
+ log.debug("304 response did not contain ETag matching one sent in If-None-Match");
+ callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+ return;
+ }
+ final HttpCacheEntry matchedEntry = matchingVariant.getEntry();
+ if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
+ final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
+ scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
+ callBackend(target, unconditional, entityProducer, scope, chain, asyncExecCallback);
+ return;
+ }
+ recordCacheUpdate(scope.clientContext);
+
+ HttpCacheEntry responseEntry = matchedEntry;
+ try {
+ responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest,
+ matchedEntry, backendResponse, requestDate, responseDateRef.get(), matchingVariant.getCacheKey());
+ } catch (final IOException ioe) {
+ log.warn("Could not processChallenge cache entry", ioe);
+ }
+
+ if (shouldSendNotModifiedResponse(request, responseEntry)) {
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(responseEntry);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ } else {
+ final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, responseEntry);
+ tryToUpdateVariantMap(target, request, matchingVariant);
+ triggerResponse(cacheResponse, scope, asyncExecCallback);
+ }
+ } catch (final HttpException | IOException ex) {
+ asyncExecCallback.failed(ex);
+ }
+ } else {
+ asyncExecCallback.completed();
+ }
+ }
+
+ @Override
+ public void failed(final Exception cause) {
+ asyncExecCallback.failed(cause);
+ }
+
+ });
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3ba71acb/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 5622e2a..e4b59e4 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -29,18 +29,14 @@ package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
-import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
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.HeaderConstants;
-import org.apache.hc.client5.http.cache.HttpCacheContext;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceFactory;
@@ -49,35 +45,25 @@ import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.impl.classic.ClassicRequestCopier;
import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpMessage;
import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
-import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
-import org.apache.hc.core5.http.message.MessageSupport;
-import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.ByteArrayBuffer;
-import org.apache.hc.core5.util.VersionInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -110,56 +96,17 @@ import org.apache.logging.log4j.Logger;
* @since 4.3
*/
@Contract(threading = ThreadingBehavior.SAFE) // So long as the responseCache implementation is threadsafe
-public class CachingExec implements ExecChainHandler {
+public class CachingExec extends CachingExecBase implements ExecChainHandler {
- private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
-
- private final AtomicLong cacheHits = new AtomicLong();
- private final AtomicLong cacheMisses = new AtomicLong();
- private final AtomicLong cacheUpdates = new AtomicLong();
-
- private final Map<ProtocolVersion, String> viaHeaders = new HashMap<>(4);
-
- private final CacheConfig cacheConfig;
- private final HttpCache responseCache;
- private final CacheValidityPolicy validityPolicy;
- private final CachedHttpResponseGenerator responseGenerator;
- private final CacheableRequestPolicy cacheableRequestPolicy;
- private final CachedResponseSuitabilityChecker suitabilityChecker;
private final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder;
- private final ResponseProtocolCompliance responseCompliance;
- private final RequestProtocolCompliance requestCompliance;
- private final ResponseCachingPolicy responseCachingPolicy;
-
- private final AsynchronousValidator asynchRevalidator;
private final Logger log = LogManager.getLogger(getClass());
public CachingExec(
final HttpCache cache,
final CacheConfig config) {
- this(cache, config, null);
- }
-
- public CachingExec(
- final HttpCache cache,
- final CacheConfig config,
- final AsynchronousValidator asynchRevalidator) {
- super();
- Args.notNull(cache, "HttpCache");
- this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
- this.responseCache = cache;
- this.validityPolicy = new CacheValidityPolicy();
- this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
- this.cacheableRequestPolicy = new CacheableRequestPolicy();
- this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig);
+ super(cache, config);
this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(ClassicRequestCopier.INSTANCE);
- this.responseCompliance = new ResponseProtocolCompliance();
- this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed());
- this.responseCachingPolicy = new ResponseCachingPolicy(
- this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
- this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled());
- this.asynchRevalidator = asynchRevalidator;
}
public CachingExec(
@@ -183,46 +130,10 @@ public class CachingExec implements ExecChainHandler {
final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
final ResponseProtocolCompliance responseCompliance,
final RequestProtocolCompliance requestCompliance,
- final CacheConfig config,
- final AsynchronousValidator asynchRevalidator) {
- this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
- this.responseCache = responseCache;
- this.validityPolicy = validityPolicy;
- this.responseCachingPolicy = responseCachingPolicy;
- this.responseGenerator = responseGenerator;
- this.cacheableRequestPolicy = cacheableRequestPolicy;
- this.suitabilityChecker = suitabilityChecker;
+ final CacheConfig config) {
+ super(responseCache, validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
+ suitabilityChecker, responseCompliance, requestCompliance, config);
this.conditionalRequestBuilder = conditionalRequestBuilder;
- this.responseCompliance = responseCompliance;
- this.requestCompliance = requestCompliance;
- this.asynchRevalidator = asynchRevalidator;
- }
-
- /**
- * Reports the number of times that the cache successfully responded
- * to an {@link HttpRequest} without contacting the origin server.
- * @return the number of cache hits
- */
- public long getCacheHits() {
- return cacheHits.get();
- }
-
- /**
- * Reports the number of times that the cache contacted the origin
- * server because it had no appropriate response cached.
- * @return the number of cache misses
- */
- public long getCacheMisses() {
- return cacheMisses.get();
- }
-
- /**
- * Reports the number of times that the cache was able to satisfy
- * a response by revalidating an existing but stale cache entry.
- * @return the number of cache revalidations
- */
- public long getCacheUpdates() {
- return cacheUpdates.get();
}
@Override
@@ -235,6 +146,8 @@ public class CachingExec implements ExecChainHandler {
final HttpRoute route = scope.route;
final HttpClientContext context = scope.clientContext;
+ context.setAttribute(HttpClientContext.HTTP_ROUTE, scope.route);
+ context.setAttribute(HttpClientContext.HTTP_REQUEST, request);
final URIAuthority authority = request.getAuthority();
final String scheme = request.getScheme();
@@ -268,12 +181,7 @@ public class CachingExec implements ExecChainHandler {
log.debug("Cache miss");
return handleCacheMiss(target, request, scope, chain);
} else {
- try {
- return handleCacheHit(target, request, scope, chain, entry);
- } catch (final ResourceIOException ex) {
- log.debug("Cache resource I/O error");
- return handleCacheFailure(target, request, scope, chain);
- }
+ return handleCacheHit(target, request, scope, chain, entry);
}
}
@@ -322,53 +230,43 @@ public class CachingExec implements ExecChainHandler {
final ExecChain.Scope scope,
final ExecChain chain,
final HttpCacheEntry entry) throws IOException, HttpException {
- final HttpRoute route = scope.route;
final HttpClientContext context = scope.clientContext;
+ context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
recordCacheHit(target, request);
- ClassicHttpResponse out;
final Date now = getCurrentDate();
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
log.debug("Cache hit");
- out = convert(generateCachedResponse(request, context, entry, now));
+ try {
+ final ClassicHttpResponse response = convert(generateCachedResponse(request, context, entry, now));
+ context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
+ return response;
+ } catch (final ResourceIOException ex) {
+ recordCacheFailure(target, request);
+ if (!mayCallBackend(request)) {
+ final ClassicHttpResponse response = convert(generateGatewayTimeout(context));
+ context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
+ return response;
+ } else {
+ setResponseStatus(scope.clientContext, CacheResponseStatus.FAILURE);
+ return chain.proceed(request, scope);
+ }
+ }
} else if (!mayCallBackend(request)) {
log.debug("Cache entry not suitable but only-if-cached requested");
- out = convert(generateGatewayTimeout(context));
- } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED
- && !suitabilityChecker.isConditional(request))) {
+ final ClassicHttpResponse response = convert(generateGatewayTimeout(context));
+ context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
+ return response;
+ } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
log.debug("Revalidating cache entry");
- return revalidateCacheEntry(target, request, scope, chain, entry, now);
+ try {
+ return revalidateCacheEntry(target, request, scope, chain, entry);
+ } catch (final IOException ioex) {
+ return convert(handleRevalidationFailure(request, context, entry, now));
+ }
} else {
log.debug("Cache entry not usable; calling backend");
return callBackend(target, request, scope, chain);
}
- context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
- context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
- context.setAttribute(HttpCoreContext.HTTP_RESPONSE, out);
- return out;
- }
-
- private ClassicHttpResponse revalidateCacheEntry(
- final HttpHost target,
- final ClassicHttpRequest request,
- final ExecChain.Scope scope,
- final ExecChain chain,
- final HttpCacheEntry entry,
- final Date now) throws HttpException, IOException {
-
- final HttpClientContext context = scope.clientContext;
- try {
- if (asynchRevalidator != null
- && !staleResponseNotAllowed(request, entry, now)
- && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
- log.trace("Serving stale with asynchronous revalidation");
- final SimpleHttpResponse resp = generateCachedResponse(request, context, entry, now);
- asynchRevalidator.revalidateCacheEntry(this, target, request, scope, chain, entry);
- return convert(resp);
- }
- return revalidateCacheEntry(target, request, scope, chain, entry);
- } catch (final IOException ioex) {
- return convert(handleRevalidationFailure(request, context, entry, now));
- }
}
ClassicHttpResponse revalidateCacheEntry(
@@ -463,7 +361,8 @@ public class CachingExec implements ExecChainHandler {
final HttpRequest request,
final ClassicHttpResponse backendResponse,
final Date requestSent,
- final Date responseReceived) throws IOException { final ByteArrayBuffer buf;
+ final Date responseReceived) throws IOException {
+ final ByteArrayBuffer buf;
final HttpEntity entity = backendResponse.getEntity();
if (entity != null) {
buf = new ByteArrayBuffer(1024);
@@ -482,16 +381,6 @@ public class CachingExec implements ExecChainHandler {
} else {
buf = null;
}
- if (buf != null && isIncompleteResponse(backendResponse, buf)) {
- final Header h = backendResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
- final ClassicHttpResponse error = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
- final String msg = String.format("Received incomplete response " +
- "with Content-Length %s but actual body length %d",
- h != null ? h.getValue() : null, buf.length());
- error.setEntity(new StringEntity(msg, ContentType.TEXT_PLAIN));
- backendResponse.close();
- return error;
- }
backendResponse.close();
final HttpCacheEntry entry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived);
return convert(responseGenerator.generateResponse(request, entry));
@@ -574,12 +463,11 @@ public class CachingExec implements ExecChainHandler {
backendResponse.close();
}
- final SimpleHttpResponse resp = responseGenerator.generateResponse(request, responseEntry);
- tryToUpdateVariantMap(target, request, matchingVariant);
-
if (shouldSendNotModifiedResponse(request, responseEntry)) {
return convert(responseGenerator.generateNotModifiedResponse(responseEntry));
}
+ final SimpleHttpResponse resp = responseGenerator.generateResponse(request, responseEntry);
+ tryToUpdateVariantMap(target, request, matchingVariant);
return convert(resp);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();
@@ -587,348 +475,4 @@ public class CachingExec implements ExecChainHandler {
}
}
- private ClassicHttpResponse handleCacheFailure(
- final HttpHost target,
- final ClassicHttpRequest request,
- final ExecChain.Scope scope,
- final ExecChain chain) throws IOException, HttpException {
- recordCacheFailure(target, request);
-
- if (!mayCallBackend(request)) {
- return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
- }
-
- setResponseStatus(scope.clientContext, CacheResponseStatus.FAILURE);
- return chain.proceed(request, scope);
- }
-
- private HttpCacheEntry satisfyFromCache(final HttpHost target, final HttpRequest request) {
- HttpCacheEntry entry = null;
- try {
- entry = responseCache.getCacheEntry(target, request);
- } catch (final IOException ioe) {
- log.warn("Unable to retrieve entries from cache", ioe);
- }
- return entry;
- }
-
- private SimpleHttpResponse getFatallyNoncompliantResponse(
- final HttpRequest request,
- final HttpContext context) {
- final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
- if (fatalError != null && !fatalError.isEmpty()) {
- setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- return responseGenerator.getErrorForRequest(fatalError.get(0));
- } else {
- return null;
- }
- }
-
- private Map<String, Variant> getExistingCacheVariants(final HttpHost target, final HttpRequest request) {
- Map<String,Variant> variants = null;
- try {
- variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
- } catch (final IOException ioe) {
- log.warn("Unable to retrieve variant entries from cache", ioe);
- }
- return variants;
- }
-
- private void recordCacheMiss(final HttpHost target, final HttpRequest request) {
- cacheMisses.getAndIncrement();
- if (log.isTraceEnabled()) {
- log.trace("Cache miss [host: " + target + "; uri: " + request.getRequestUri() + "]");
- }
- }
-
- private void recordCacheHit(final HttpHost target, final HttpRequest request) {
- cacheHits.getAndIncrement();
- if (log.isTraceEnabled()) {
- log.trace("Cache hit [host: " + target + "; uri: " + request.getRequestUri() + "]");
- }
- }
-
- private void recordCacheFailure(final HttpHost target, final HttpRequest request) {
- cacheMisses.getAndIncrement();
- if (log.isTraceEnabled()) {
- log.trace("Cache failure [host: " + target + "; uri: " + request.getRequestUri() + "]");
- }
- }
-
- private void recordCacheUpdate(final HttpContext context) {
- cacheUpdates.getAndIncrement();
- setResponseStatus(context, CacheResponseStatus.VALIDATED);
- }
-
- private void flushEntriesInvalidatedByRequest(final HttpHost target, final HttpRequest request) {
- try {
- responseCache.flushInvalidatedCacheEntriesFor(target, request);
- } catch (final IOException ioe) {
- log.warn("Unable to flush invalidated entries from cache", ioe);
- }
- }
-
- private SimpleHttpResponse generateCachedResponse(
- final HttpRequest request,
- final HttpContext context,
- final HttpCacheEntry entry,
- final Date now) throws IOException {
- final SimpleHttpResponse cachedResponse;
- if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
- || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
- cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
- } else {
- cachedResponse = responseGenerator.generateResponse(request, entry);
- }
- setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
- if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
- cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
- }
- return cachedResponse;
- }
-
- private SimpleHttpResponse handleRevalidationFailure(
- final HttpRequest request,
- final HttpContext context,
- final HttpCacheEntry entry,
- final Date now) throws IOException {
- if (staleResponseNotAllowed(request, entry, now)) {
- return generateGatewayTimeout(context);
- } else {
- return unvalidatedCacheHit(request, context, entry);
- }
- }
-
- private SimpleHttpResponse generateGatewayTimeout(
- final HttpContext context) {
- setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
- return SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
- }
-
- private SimpleHttpResponse unvalidatedCacheHit(
- final HttpRequest request,
- final HttpContext context,
- final HttpCacheEntry entry) throws IOException {
- final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, entry);
- setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
- cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
- return cachedResponse;
- }
-
- private boolean staleResponseNotAllowed(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
- return validityPolicy.mustRevalidate(entry)
- || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
- || explicitFreshnessRequest(request, entry, now);
- }
-
- private boolean mayCallBackend(final HttpRequest request) {
- final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
- while (it.hasNext()) {
- final HeaderElement elt = it.next();
- if ("only-if-cached".equals(elt.getName())) {
- log.trace("Request marked only-if-cached");
- return false;
- }
- }
- return true;
- }
-
- private boolean explicitFreshnessRequest(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
- final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
- while (it.hasNext()) {
- final HeaderElement elt = it.next();
- if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
- try {
- final int maxstale = Integer.parseInt(elt.getValue());
- final long age = validityPolicy.getCurrentAgeSecs(entry, now);
- final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
- if (age - lifetime > maxstale) {
- return true;
- }
- } catch (final NumberFormatException nfe) {
- return true;
- }
- } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())
- || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
- return true;
- }
- }
- return false;
- }
-
- private String generateViaHeader(final HttpMessage msg) {
-
- if (msg.getVersion() == null) {
- msg.setVersion(HttpVersion.DEFAULT);
- }
- final ProtocolVersion pv = msg.getVersion();
- final String existingEntry = viaHeaders.get(msg.getVersion());
- if (existingEntry != null) {
- return existingEntry;
- }
-
- final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.hc.client5", getClass().getClassLoader());
- final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
-
- final String value;
- final int major = pv.getMajor();
- final int minor = pv.getMinor();
- if ("http".equalsIgnoreCase(pv.getProtocol())) {
- value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", major, minor,
- release);
- } else {
- value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), major,
- minor, release);
- }
- viaHeaders.put(pv, value);
-
- return value;
- }
-
- private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
- if (context != null) {
- context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
- }
- }
-
- /**
- * Reports whether this {@code CachingHttpClient} implementation
- * supports byte-range requests as specified by the {@code Range}
- * and {@code Content-Range} headers.
- * @return {@code true} if byte-range requests are supported
- */
- public boolean supportsRangeAndContentRangeHeaders() {
- return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
- }
-
- Date getCurrentDate() {
- return new Date();
- }
-
- boolean clientRequestsOurOptions(final HttpRequest request) {
- if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
- return false;
- }
-
- if (!"*".equals(request.getRequestUri())) {
- return false;
- }
-
- if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) {
- return false;
- }
-
- return true;
- }
-
- private boolean revalidationResponseIsTooOld(final HttpResponse backendResponse,
- final HttpCacheEntry cacheEntry) {
- final Header entryDateHeader = cacheEntry.getFirstHeader(HttpHeaders.DATE);
- final Header responseDateHeader = backendResponse.getFirstHeader(HttpHeaders.DATE);
- if (entryDateHeader != null && responseDateHeader != null) {
- final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
- final Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
- if (entryDate == null || respDate == null) {
- // either backend response or cached entry did not have a valid
- // Date header, so we can't tell if they are out of order
- // according to the origin clock; thus we can skip the
- // unconditional retry recommended in 13.2.6 of RFC 2616.
- return false;
- }
- if (respDate.before(entryDate)) {
- return true;
- }
- }
- return false;
- }
-
- private void tryToUpdateVariantMap(
- final HttpHost target,
- final HttpRequest request,
- final Variant matchingVariant) {
- try {
- responseCache.reuseVariantEntryFor(target, request, matchingVariant);
- } catch (final IOException ioe) {
- log.warn("Could not processChallenge cache entry to reuse variant", ioe);
- }
- }
-
- private boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry) {
- return (suitabilityChecker.isConditional(request)
- && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
- }
-
- private boolean staleIfErrorAppliesTo(final int statusCode) {
- return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
- || statusCode == HttpStatus.SC_BAD_GATEWAY
- || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
- || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
- }
-
- boolean isIncompleteResponse(final HttpResponse resp, final ByteArrayBuffer buffer) {
- if (buffer == null) {
- return false;
- }
- final int status = resp.getCode();
- if (status != HttpStatus.SC_OK && status != HttpStatus.SC_PARTIAL_CONTENT) {
- return false;
- }
- final Header hdr = resp.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
- if (hdr == null) {
- return false;
- }
- final int contentLength;
- try {
- contentLength = Integer.parseInt(hdr.getValue());
- } catch (final NumberFormatException nfe) {
- return false;
- }
- return buffer.length() < contentLength;
- }
-
- /**
- * For 304 Not modified responses, adds a "Last-Modified" header with the
- * value of the "If-Modified-Since" header passed in the request. This
- * header is required to be able to reuse match the cache entry for
- * subsequent requests but as defined in http specifications it is not
- * included in 304 responses by backend servers. This header will not be
- * included in the resulting response.
- */
- private void storeRequestIfModifiedSinceFor304Response(
- final HttpRequest request, final HttpResponse backendResponse) {
- if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
- final Header h = request.getFirstHeader("If-Modified-Since");
- if (h != null) {
- backendResponse.addHeader("Last-Modified", h.getValue());
- }
- }
- }
-
- private boolean alreadyHaveNewerCacheEntry(
- final HttpHost target, final HttpRequest request, final HttpResponse backendResponse) {
- HttpCacheEntry existing = null;
- try {
- existing = responseCache.getCacheEntry(target, request);
- } catch (final IOException ioe) {
- // nop
- }
- if (existing == null) {
- return false;
- }
- final Header entryDateHeader = existing.getFirstHeader(HttpHeaders.DATE);
- if (entryDateHeader == null) {
- return false;
- }
- final Header responseDateHeader = backendResponse.getFirstHeader(HttpHeaders.DATE);
- if (responseDateHeader == null) {
- return false;
- }
- final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
- final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
- if (entryDate == null || responseDate == null) {
- return false;
- }
- return responseDate.before(entryDate);
- }
-
}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3ba71acb/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
new file mode 100644
index 0000000..5117eb8
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -0,0 +1,453 @@
+/*
+ * ====================================================================
+ * 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 java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.cache.CacheResponseStatus;
+import org.apache.hc.client5.http.cache.HeaderConstants;
+import org.apache.hc.client5.http.cache.HttpCacheContext;
+import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HeaderElement;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpMessage;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.message.MessageSupport;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.VersionInfo;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class CachingExecBase {
+
+ final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
+
+ final AtomicLong cacheHits = new AtomicLong();
+ final AtomicLong cacheMisses = new AtomicLong();
+ final AtomicLong cacheUpdates = new AtomicLong();
+
+ final Map<ProtocolVersion, String> viaHeaders = new HashMap<>(4);
+
+ final HttpCache responseCache;
+ final ResponseCachingPolicy responseCachingPolicy;
+ final CacheValidityPolicy validityPolicy;
+ final CachedHttpResponseGenerator responseGenerator;
+ final CacheableRequestPolicy cacheableRequestPolicy;
+ final CachedResponseSuitabilityChecker suitabilityChecker;
+ final ResponseProtocolCompliance responseCompliance;
+ final RequestProtocolCompliance requestCompliance;
+ final CacheConfig cacheConfig;
+
+ final Logger log = LogManager.getLogger(getClass());
+
+ CachingExecBase(
+ final HttpCache responseCache,
+ final CacheValidityPolicy validityPolicy,
+ final ResponseCachingPolicy responseCachingPolicy,
+ final CachedHttpResponseGenerator responseGenerator,
+ final CacheableRequestPolicy cacheableRequestPolicy,
+ final CachedResponseSuitabilityChecker suitabilityChecker,
+ final ResponseProtocolCompliance responseCompliance,
+ final RequestProtocolCompliance requestCompliance,
+ final CacheConfig config) {
+ this.responseCache = responseCache;
+ this.responseCachingPolicy = responseCachingPolicy;
+ this.validityPolicy = validityPolicy;
+ this.responseGenerator = responseGenerator;
+ this.cacheableRequestPolicy = cacheableRequestPolicy;
+ this.suitabilityChecker = suitabilityChecker;
+ this.requestCompliance = requestCompliance;
+ this.responseCompliance = responseCompliance;
+ this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
+ }
+
+ public CachingExecBase(final HttpCache cache, final CacheConfig config) {
+ super();
+ this.responseCache = Args.notNull(cache, "Response cache");
+ this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
+ this.validityPolicy = new CacheValidityPolicy();
+ this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
+ this.cacheableRequestPolicy = new CacheableRequestPolicy();
+ this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig);
+ this.responseCompliance = new ResponseProtocolCompliance();
+ this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed());
+ this.responseCachingPolicy = new ResponseCachingPolicy(
+ this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
+ this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled());
+ }
+
+ /**
+ * Reports the number of times that the cache successfully responded
+ * to an {@link HttpRequest} without contacting the origin server.
+ * @return the number of cache hits
+ */
+ public long getCacheHits() {
+ return cacheHits.get();
+ }
+
+ /**
+ * Reports the number of times that the cache contacted the origin
+ * server because it had no appropriate response cached.
+ * @return the number of cache misses
+ */
+ public long getCacheMisses() {
+ return cacheMisses.get();
+ }
+
+ /**
+ * Reports the number of times that the cache was able to satisfy
+ * a response by revalidating an existing but stale cache entry.
+ * @return the number of cache revalidations
+ */
+ public long getCacheUpdates() {
+ return cacheUpdates.get();
+ }
+
+ HttpCacheEntry satisfyFromCache(final HttpHost target, final HttpRequest request) {
+ HttpCacheEntry entry = null;
+ try {
+ entry = responseCache.getCacheEntry(target, request);
+ } catch (final IOException ioe) {
+ log.warn("Unable to retrieve entries from cache", ioe);
+ }
+ return entry;
+ }
+
+ SimpleHttpResponse getFatallyNoncompliantResponse(
+ final HttpRequest request,
+ final HttpContext context) {
+ final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
+ if (fatalError != null && !fatalError.isEmpty()) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ return responseGenerator.getErrorForRequest(fatalError.get(0));
+ } else {
+ return null;
+ }
+ }
+
+ Map<String, Variant> getExistingCacheVariants(final HttpHost target, final HttpRequest request) {
+ Map<String,Variant> variants = null;
+ try {
+ variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
+ } catch (final IOException ioe) {
+ log.warn("Unable to retrieve variant entries from cache", ioe);
+ }
+ return variants;
+ }
+
+ void recordCacheMiss(final HttpHost target, final HttpRequest request) {
+ cacheMisses.getAndIncrement();
+ if (log.isTraceEnabled()) {
+ log.trace("Cache miss [host: " + target + "; uri: " + request.getRequestUri() + "]");
+ }
+ }
+
+ void recordCacheHit(final HttpHost target, final HttpRequest request) {
+ cacheHits.getAndIncrement();
+ if (log.isTraceEnabled()) {
+ log.trace("Cache hit [host: " + target + "; uri: " + request.getRequestUri() + "]");
+ }
+ }
+
+ void recordCacheFailure(final HttpHost target, final HttpRequest request) {
+ cacheMisses.getAndIncrement();
+ if (log.isTraceEnabled()) {
+ log.trace("Cache failure [host: " + target + "; uri: " + request.getRequestUri() + "]");
+ }
+ }
+
+ void recordCacheUpdate(final HttpContext context) {
+ cacheUpdates.getAndIncrement();
+ setResponseStatus(context, CacheResponseStatus.VALIDATED);
+ }
+
+ void flushEntriesInvalidatedByRequest(final HttpHost target, final HttpRequest request) {
+ try {
+ responseCache.flushInvalidatedCacheEntriesFor(target, request);
+ } catch (final IOException ioe) {
+ log.warn("Unable to flush invalidated entries from cache", ioe);
+ }
+ }
+
+ SimpleHttpResponse generateCachedResponse(
+ final HttpRequest request,
+ final HttpContext context,
+ final HttpCacheEntry entry,
+ final Date now) throws IOException {
+ final SimpleHttpResponse cachedResponse;
+ if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
+ || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
+ cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
+ } else {
+ cachedResponse = responseGenerator.generateResponse(request, entry);
+ }
+ setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+ if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
+ cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
+ }
+ return cachedResponse;
+ }
+
+ SimpleHttpResponse handleRevalidationFailure(
+ final HttpRequest request,
+ final HttpContext context,
+ final HttpCacheEntry entry,
+ final Date now) throws IOException {
+ if (staleResponseNotAllowed(request, entry, now)) {
+ return generateGatewayTimeout(context);
+ } else {
+ return unvalidatedCacheHit(request, context, entry);
+ }
+ }
+
+ SimpleHttpResponse generateGatewayTimeout(
+ final HttpContext context) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ return SimpleHttpResponse.create(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
+ }
+
+ SimpleHttpResponse unvalidatedCacheHit(
+ final HttpRequest request,
+ final HttpContext context,
+ final HttpCacheEntry entry) throws IOException {
+ final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, entry);
+ setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+ cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
+ return cachedResponse;
+ }
+
+ boolean staleResponseNotAllowed(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
+ return validityPolicy.mustRevalidate(entry)
+ || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
+ || explicitFreshnessRequest(request, entry, now);
+ }
+
+ boolean mayCallBackend(final HttpRequest request) {
+ final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
+ while (it.hasNext()) {
+ final HeaderElement elt = it.next();
+ if ("only-if-cached".equals(elt.getName())) {
+ log.trace("Request marked only-if-cached");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean explicitFreshnessRequest(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
+ final Iterator<HeaderElement> it = MessageSupport.iterate(request, HeaderConstants.CACHE_CONTROL);
+ while (it.hasNext()) {
+ final HeaderElement elt = it.next();
+ if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
+ try {
+ final int maxstale = Integer.parseInt(elt.getValue());
+ final long age = validityPolicy.getCurrentAgeSecs(entry, now);
+ final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
+ if (age - lifetime > maxstale) {
+ return true;
+ }
+ } catch (final NumberFormatException nfe) {
+ return true;
+ }
+ } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())
+ || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ String generateViaHeader(final HttpMessage msg) {
+
+ if (msg.getVersion() == null) {
+ msg.setVersion(HttpVersion.DEFAULT);
+ }
+ final ProtocolVersion pv = msg.getVersion();
+ final String existingEntry = viaHeaders.get(msg.getVersion());
+ if (existingEntry != null) {
+ return existingEntry;
+ }
+
+ final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.hc.client5", getClass().getClassLoader());
+ final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
+
+ final String value;
+ final int major = pv.getMajor();
+ final int minor = pv.getMinor();
+ if ("http".equalsIgnoreCase(pv.getProtocol())) {
+ value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", major, minor,
+ release);
+ } else {
+ value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), major,
+ minor, release);
+ }
+ viaHeaders.put(pv, value);
+
+ return value;
+ }
+
+ void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
+ if (context != null) {
+ context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
+ }
+ }
+
+ /**
+ * Reports whether this {@code CachingHttpClient} implementation
+ * supports byte-range requests as specified by the {@code Range}
+ * and {@code Content-Range} headers.
+ * @return {@code true} if byte-range requests are supported
+ */
+ public boolean supportsRangeAndContentRangeHeaders() {
+ return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
+ }
+
+ Date getCurrentDate() {
+ return new Date();
+ }
+
+ boolean clientRequestsOurOptions(final HttpRequest request) {
+ if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
+ return false;
+ }
+
+ if (!"*".equals(request.getRequestUri())) {
+ return false;
+ }
+
+ if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ boolean revalidationResponseIsTooOld(final HttpResponse backendResponse,
+ final HttpCacheEntry cacheEntry) {
+ final Header entryDateHeader = cacheEntry.getFirstHeader(HttpHeaders.DATE);
+ final Header responseDateHeader = backendResponse.getFirstHeader(HttpHeaders.DATE);
+ if (entryDateHeader != null && responseDateHeader != null) {
+ final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+ final Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
+ if (entryDate == null || respDate == null) {
+ // either backend response or cached entry did not have a valid
+ // Date header, so we can't tell if they are out of order
+ // according to the origin clock; thus we can skip the
+ // unconditional retry recommended in 13.2.6 of RFC 2616.
+ return false;
+ }
+ if (respDate.before(entryDate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void tryToUpdateVariantMap(
+ final HttpHost target,
+ final HttpRequest request,
+ final Variant matchingVariant) {
+ try {
+ responseCache.reuseVariantEntryFor(target, request, matchingVariant);
+ } catch (final IOException ioe) {
+ log.warn("Could not processChallenge cache entry to reuse variant", ioe);
+ }
+ }
+
+ boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry) {
+ return (suitabilityChecker.isConditional(request)
+ && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
+ }
+
+ boolean staleIfErrorAppliesTo(final int statusCode) {
+ return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+ || statusCode == HttpStatus.SC_BAD_GATEWAY
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+ || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
+ }
+
+ /**
+ * For 304 Not modified responses, adds a "Last-Modified" header with the
+ * value of the "If-Modified-Since" header passed in the request. This
+ * header is required to be able to reuse match the cache entry for
+ * subsequent requests but as defined in http specifications it is not
+ * included in 304 responses by backend servers. This header will not be
+ * included in the resulting response.
+ */
+ void storeRequestIfModifiedSinceFor304Response(
+ final HttpRequest request, final HttpResponse backendResponse) {
+ if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
+ final Header h = request.getFirstHeader("If-Modified-Since");
+ if (h != null) {
+ backendResponse.addHeader("Last-Modified", h.getValue());
+ }
+ }
+ }
+
+ boolean alreadyHaveNewerCacheEntry(
+ final HttpHost target, final HttpRequest request, final HttpResponse backendResponse) {
+ HttpCacheEntry existing = null;
+ try {
+ existing = responseCache.getCacheEntry(target, request);
+ } catch (final IOException ioe) {
+ // nop
+ }
+ if (existing == null) {
+ return false;
+ }
+ final Header entryDateHeader = existing.getFirstHeader(HttpHeaders.DATE);
+ if (entryDateHeader == null) {
+ return false;
+ }
+ final Header responseDateHeader = backendResponse.getFirstHeader(HttpHeaders.DATE);
+ if (responseDateHeader == null) {
+ return false;
+ }
+ final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+ final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
+ if (entryDate == null || responseDate == null) {
+ return false;
+ }
+ return responseDate.before(entryDate);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3ba71acb/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
index 8f5babf..3a221e1 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
@@ -50,7 +50,6 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
private HttpCacheStorage storage;
private File cacheDir;
private CacheConfig cacheConfig;
- private SchedulingStrategy schedulingStrategy;
private HttpCacheInvalidator httpCacheInvalidator;
private boolean deleteCache;
@@ -87,12 +86,6 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
return this;
}
- public final CachingHttpClientBuilder setSchedulingStrategy(
- final SchedulingStrategy schedulingStrategy) {
- this.schedulingStrategy = schedulingStrategy;
- return this;
- }
-
public final CachingHttpClientBuilder setHttpCacheInvalidator(
final HttpCacheInvalidator cacheInvalidator) {
this.httpCacheInvalidator = cacheInvalidator;
@@ -137,13 +130,6 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
storageCopy = managedStorage;
}
}
- final AsynchronousValidator revalidator;
- if (config.getAsynchronousWorkersMax() > 0) {
- revalidator = new AsynchronousValidator(schedulingStrategy != null ? schedulingStrategy : new ImmediateSchedulingStrategy(config));
- addCloseable(revalidator);
- } else {
- revalidator = null;
- }
final CacheKeyGenerator uriExtractor = new CacheKeyGenerator();
final HttpCache httpCache = new BasicHttpCache(
resourceFactoryCopy,
@@ -151,7 +137,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
uriExtractor,
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new CacheInvalidator(uriExtractor, storageCopy));
- final CachingExec cachingExec = new CachingExec(httpCache, config, revalidator);
+ final CachingExec cachingExec = new CachingExec(httpCache, config);
execChainDefinition.addAfter(ChainElements.PROTOCOL.name(), cachingExec, "CACHING");
}