From fcd02a0a18c3404e338c0749d2f29dec21fe7df7 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sat, 8 Dec 2012 19:59:24 +0000
Subject: [PATCH] Ported CachingHttpClient to ClientExecChain API
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1418743 13f79535-47bb-0310-9956-ffa450edef68
---
.../cache/AsynchronousValidationRequest.java | 41 +-
.../client/cache/AsynchronousValidator.java | 39 +-
.../http/impl/client/cache/CachingExec.java | 830 +++++++
.../impl/client/cache/CachingHttpClient.java | 158 ++
.../http/impl/client/cache/Proxies.java | 30 +
.../client/cache/ResponseProxyHandler.java | 89 +
.../memcached/MemcachedHttpCacheStorage.java | 13 +-
.../client/cache/AbstractProtocolTest.java | 51 +-
.../cache/DoNotTestProtocolRequirements.java | 186 --
.../http/impl/client/cache/DummyBackend.java | 70 +
.../impl/client/cache/DummyHttpClient.java | 142 --
.../TestAsynchronousValidationRequest.java | 76 +-
.../cache/TestAsynchronousValidator.java | 46 +-
...ngHttpClient.java => TestCachingExec.java} | 1345 ++++-------
.../cache/TestHttpCacheJiraNumber1147.java | 55 +-
.../cache/TestProtocolAllowedBehavior.java | 22 +-
.../client/cache/TestProtocolDeviations.java | 116 +-
.../cache/TestProtocolRecommendations.java | 565 +++--
.../cache/TestProtocolRequirements.java | 2067 ++++++++++-------
.../client/cache/TestRFC5861Compliance.java | 190 +-
.../TestEhcacheProtocolRequirements.java | 8 +-
.../TestMemcachedHttpCacheStorage.java | 64 +-
.../impl/client/execchain/MainClientExec.java | 8 +-
.../{ExecProxies.java => Proxies.java} | 2 +-
.../impl/client/execchain/RedirectExec.java | 2 +-
.../http/impl/client/execchain/RetryExec.java | 2 +-
26 files changed, 3622 insertions(+), 2595 deletions(-)
create mode 100644 httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
create mode 100644 httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java
create mode 100644 httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java
delete mode 100644 httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
create mode 100644 httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java
delete mode 100644 httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java
rename httpclient-cache/src/test/java/org/apache/http/impl/client/cache/{TestCachingHttpClient.java => TestCachingExec.java} (53%)
rename httpclient/src/main/java/org/apache/http/impl/client/execchain/{ExecProxies.java => Proxies.java} (99%)
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
index 4b498867c..0d1b8d8eb 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
@@ -29,11 +29,12 @@ package org.apache.http.impl.client.cache;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.http.HttpHost;
-import org.apache.http.ProtocolException;
+import org.apache.http.HttpException;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
-import org.apache.http.protocol.HttpContext;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
/**
* Class used to represent an asynchronous revalidation event, such as with
@@ -41,10 +42,11 @@ import org.apache.http.protocol.HttpContext;
*/
class AsynchronousValidationRequest implements Runnable {
private final AsynchronousValidator parent;
- private final CachingHttpClient cachingClient;
- private final HttpHost target;
+ private final CachingExec cachingExec;
+ private final HttpRoute route;
private final HttpRequestWrapper request;
- private final HttpContext context;
+ private final HttpClientContext context;
+ private final HttpExecutionAware execAware;
private final HttpCacheEntry cacheEntry;
private final String identifier;
@@ -61,27 +63,32 @@ class AsynchronousValidationRequest implements Runnable {
* @param bookKeeping
* @param identifier
*/
- AsynchronousValidationRequest(AsynchronousValidator parent,
- CachingHttpClient cachingClient, HttpHost target,
- HttpRequestWrapper request, HttpContext context,
- HttpCacheEntry cacheEntry,
- String identifier) {
+ AsynchronousValidationRequest(
+ final AsynchronousValidator parent,
+ final CachingExec cachingExec,
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry cacheEntry,
+ final String identifier) {
this.parent = parent;
- this.cachingClient = cachingClient;
- this.target = target;
+ this.cachingExec = cachingExec;
+ this.route = route;
this.request = request;
this.context = context;
+ this.execAware = execAware;
this.cacheEntry = cacheEntry;
this.identifier = identifier;
}
public void run() {
try {
- cachingClient.revalidateCacheEntry(target, request, context, cacheEntry);
+ cachingExec.revalidateCacheEntry(route, request, context, execAware, cacheEntry);
} catch (IOException ioe) {
- log.debug("Asynchronous revalidation failed due to exception: " + ioe);
- } catch (ProtocolException pe) {
- log.error("ProtocolException thrown during asynchronous revalidation: " + pe);
+ log.debug("Asynchronous revalidation failed due to I/O error", ioe);
+ } catch (HttpException pe) {
+ log.error("HTTP protocol exception during asynchronous revalidation", pe);
} finally {
parent.markComplete(identifier);
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
index a4217670c..2335d327d 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
@@ -37,17 +37,18 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.http.HttpHost;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
-import org.apache.http.protocol.HttpContext;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
/**
* Class used for asynchronous revalidations to be used when the "stale-
* while-revalidate" directive is present
*/
class AsynchronousValidator {
- private final CachingHttpClient cachingClient;
+ private final CachingExec cachingExec;
private final ExecutorService executor;
private final Set queued;
private final CacheKeyGenerator cacheKeyGenerator;
@@ -59,16 +60,16 @@ class AsynchronousValidator {
* using the supplied {@link CachingHttpClient}, and
* a {@link ThreadPoolExecutor} generated according to the thread
* pool settings provided in the given {@link CacheConfig}.
- * @param cachingClient used to execute asynchronous requests
+ * @param cachingExect used to execute asynchronous requests
* @param config specifies thread pool settings. See
* {@link CacheConfig#getAsynchronousWorkersMax()},
* {@link CacheConfig#getAsynchronousWorkersCore()},
* {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
* and {@link CacheConfig#getRevalidationQueueSize()}.
*/
- public AsynchronousValidator(CachingHttpClient cachingClient,
+ public AsynchronousValidator(CachingExec cachingExect,
CacheConfig config) {
- this(cachingClient,
+ this(cachingExect,
new ThreadPoolExecutor(config.getAsynchronousWorkersCore(),
config.getAsynchronousWorkersMax(),
config.getAsynchronousWorkerIdleLifetimeSecs(),
@@ -81,12 +82,11 @@ class AsynchronousValidator {
* Create AsynchronousValidator which will make revalidation requests
* using the supplied {@link CachingHttpClient} and
* {@link ExecutorService}.
- * @param cachingClient used to execute asynchronous requests
+ * @param cachingExect used to execute asynchronous requests
* @param executor used to manage a thread pool of revalidation workers
*/
- AsynchronousValidator(CachingHttpClient cachingClient,
- ExecutorService executor) {
- this.cachingClient = cachingClient;
+ AsynchronousValidator(CachingExec cachingExec, ExecutorService executor) {
+ this.cachingExec = cachingExec;
this.executor = executor;
this.queued = new HashSet();
this.cacheKeyGenerator = new CacheKeyGenerator();
@@ -94,21 +94,20 @@ class AsynchronousValidator {
/**
* Schedules an asynchronous revalidation
- *
- * @param target
- * @param request
- * @param context
- * @param entry
*/
- public synchronized void revalidateCacheEntry(HttpHost target,
- HttpRequestWrapper request, HttpContext context, HttpCacheEntry entry) {
+ public synchronized void revalidateCacheEntry(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry entry) {
// getVariantURI will fall back on getURI if no variants exist
- String uri = cacheKeyGenerator.getVariantURI(target, request, entry);
+ String uri = cacheKeyGenerator.getVariantURI(route.getTargetHost(), request, entry);
if (!queued.contains(uri)) {
AsynchronousValidationRequest revalidationRequest =
- new AsynchronousValidationRequest(this, cachingClient, target,
- request, context, entry, uri);
+ new AsynchronousValidationRequest(
+ this, cachingExec, route, request, context, execAware, entry, uri);
try {
executor.execute(revalidationRequest);
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
new file mode 100644
index 000000000..12b7838cd
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
@@ -0,0 +1,830 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpMessage;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.RequestLine;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.client.cache.CacheResponseStatus;
+import org.apache.http.client.cache.HeaderConstants;
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.cache.ResourceFactory;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpExecutionAware;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.client.execchain.ClientExecChain;
+import org.apache.http.impl.cookie.DateParseException;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.apache.http.util.VersionInfo;
+
+/**
+ *
CachingExec is intended to transparently add client-side caching
+ * to the HttpClient {@link ClientExecChain execution chain}.
+ * 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.
+ *
+ *
Folks that would like to experiment with alternative storage backends
+ * should look at the {@link HttpCacheStorage} interface and the related
+ * package documentation there. You may also be interested in the provided
+ * {@link org.apache.http.impl.client.cache.ehcache.EhcacheHttpCacheStorage
+ * EhCache} and {@link
+ * org.apache.http.impl.client.cache.memcached.MemcachedHttpCacheStorage
+ * memcached} storage backends.
+ *
+ *
+ * @since 4.3
+ */
+@ThreadSafe // So long as the responseCache implementation is threadsafe
+public class CachingExec implements ClientExecChain {
+
+ /**
+ * This is the name under which the {@link
+ * org.apache.http.client.cache.CacheResponseStatus} of a request
+ * (for example, whether it resulted in a cache hit) will be recorded if an
+ * {@link HttpContext} is provided during execution.
+ */
+ public static final String CACHE_RESPONSE_STATUS = "http.cache.response.status";
+
+ 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 viaHeaders = new HashMap(4);
+
+ private final CacheConfig cacheConfig;
+ private final ClientExecChain backend;
+ private final HttpCache responseCache;
+ private final CacheValidityPolicy validityPolicy;
+ private final CachedHttpResponseGenerator responseGenerator;
+ private final CacheableRequestPolicy cacheableRequestPolicy;
+ private final CachedResponseSuitabilityChecker suitabilityChecker;
+ private final ConditionalRequestBuilder conditionalRequestBuilder;
+ private final ResponseProtocolCompliance responseCompliance;
+ private final RequestProtocolCompliance requestCompliance;
+ private final ResponseCachingPolicy responseCachingPolicy;
+
+ private final AsynchronousValidator asynchRevalidator;
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ public CachingExec(
+ ClientExecChain backend,
+ HttpCache cache,
+ CacheConfig config) {
+ super();
+ if (backend == null) {
+ throw new IllegalArgumentException("HTTP backend may not be null");
+ }
+ if (cache == null) {
+ throw new IllegalArgumentException("HttpCache may not be null");
+ }
+ this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
+ this.backend = backend;
+ this.responseCache = cache;
+ this.validityPolicy = new CacheValidityPolicy();
+ this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
+ this.cacheableRequestPolicy = new CacheableRequestPolicy();
+ this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, config);
+ this.conditionalRequestBuilder = new ConditionalRequestBuilder();
+ this.responseCompliance = new ResponseProtocolCompliance();
+ this.requestCompliance = new RequestProtocolCompliance();
+ this.responseCachingPolicy = new ResponseCachingPolicy(
+ this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache());
+ this.asynchRevalidator = makeAsynchronousValidator(config);
+ }
+
+ public CachingExec(
+ ClientExecChain backend,
+ ResourceFactory resourceFactory,
+ HttpCacheStorage storage,
+ CacheConfig config) {
+ this(backend, new BasicHttpCache(resourceFactory, storage, config), config);
+ }
+
+ public CachingExec(ClientExecChain backend) {
+ this(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
+ }
+
+ CachingExec(
+ ClientExecChain backend,
+ HttpCache responseCache,
+ CacheValidityPolicy validityPolicy,
+ ResponseCachingPolicy responseCachingPolicy,
+ CachedHttpResponseGenerator responseGenerator,
+ CacheableRequestPolicy cacheableRequestPolicy,
+ CachedResponseSuitabilityChecker suitabilityChecker,
+ ConditionalRequestBuilder conditionalRequestBuilder,
+ ResponseProtocolCompliance responseCompliance,
+ RequestProtocolCompliance requestCompliance,
+ CacheConfig config) {
+ this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
+ this.backend = backend;
+ this.responseCache = responseCache;
+ this.validityPolicy = validityPolicy;
+ this.responseCachingPolicy = responseCachingPolicy;
+ this.responseGenerator = responseGenerator;
+ this.cacheableRequestPolicy = cacheableRequestPolicy;
+ this.suitabilityChecker = suitabilityChecker;
+ this.conditionalRequestBuilder = conditionalRequestBuilder;
+ this.responseCompliance = responseCompliance;
+ this.requestCompliance = requestCompliance;
+ this.asynchRevalidator = makeAsynchronousValidator(config);
+ }
+
+ private AsynchronousValidator makeAsynchronousValidator(
+ CacheConfig config) {
+ if (config.getAsynchronousWorkersMax() > 0) {
+ return new AsynchronousValidator(this, config);
+ }
+ return null;
+ }
+
+ /**
+ * 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();
+ }
+
+ public CloseableHttpResponse execute(
+ final HttpRoute route,
+ final HttpRequestWrapper request) throws IOException, HttpException {
+ return execute(route, request, HttpClientContext.create(), null);
+ }
+
+ public CloseableHttpResponse execute(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context) throws IOException, HttpException {
+ return execute(route, request, context, null);
+ }
+
+ public CloseableHttpResponse execute(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware) throws IOException, HttpException {
+
+ HttpHost target = route.getTargetHost();
+ String via = generateViaHeader(request.getOriginal());
+
+ // default response context
+ setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
+
+ if (clientRequestsOurOptions(request)) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ return Proxies.enhanceResponse(new OptionsHttp11Response());
+ }
+
+ HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
+ if (fatalErrorResponse != null) {
+ return Proxies.enhanceResponse(fatalErrorResponse);
+ }
+
+ requestCompliance.makeRequestCompliant(request);
+ request.addHeader("Via",via);
+
+ flushEntriesInvalidatedByRequest(route.getTargetHost(), request);
+
+ if (!cacheableRequestPolicy.isServableFromCache(request)) {
+ log.debug("Request is not servable from cache");
+ return callBackend(route, request, context, execAware);
+ }
+
+ HttpCacheEntry entry = satisfyFromCache(target, request);
+ if (entry == null) {
+ log.debug("Cache miss");
+ return handleCacheMiss(route, request, context, execAware);
+ }
+
+ return handleCacheHit(route, request, context, execAware, entry);
+ }
+
+ private CloseableHttpResponse handleCacheHit(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry entry) throws IOException, HttpException {
+ HttpHost target = route.getTargetHost();
+ recordCacheHit(target, request);
+ CloseableHttpResponse out = null;
+ Date now = getCurrentDate();
+ if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
+ log.debug("Cache hit");
+ out = Proxies.enhanceResponse(generateCachedResponse(request, context, entry, now));
+ } else if (!mayCallBackend(request)) {
+ log.debug("Cache entry not suitable but only-if-cached requested");
+ out = Proxies.enhanceResponse(generateGatewayTimeout(context));
+ } else if (validityPolicy.isRevalidatable(entry)) {
+ log.debug("Revalidating cache entry");
+ return revalidateCacheEntry(route, request, context, execAware, entry, now);
+ } else {
+ log.debug("Cache entry not usable; calling backend");
+ return callBackend(route, request, context, execAware);
+ }
+ if (context != null) {
+ context.setAttribute(ClientContext.ROUTE, route);
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
+ context.setAttribute(ExecutionContext.HTTP_RESPONSE, out);
+ context.setAttribute(ExecutionContext.HTTP_REQ_SENT, Boolean.TRUE);
+ }
+ return out;
+ }
+
+ private CloseableHttpResponse revalidateCacheEntry(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry entry,
+ final Date now) throws IOException, HttpException {
+
+ try {
+ if (asynchRevalidator != null
+ && !staleResponseNotAllowed(request, entry, now)
+ && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
+ log.trace("Serving stale with asynchronous revalidation");
+ HttpResponse resp = generateCachedResponse(request, context, entry, now);
+ asynchRevalidator.revalidateCacheEntry(route, request, context, execAware, entry);
+ return Proxies.enhanceResponse(resp);
+ }
+ return revalidateCacheEntry(route, request, context, execAware, entry);
+ } catch (IOException ioex) {
+ return Proxies.enhanceResponse(
+ handleRevalidationFailure(request, context, entry, now));
+ }
+ }
+
+ private CloseableHttpResponse handleCacheMiss(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware) throws IOException, HttpException {
+ HttpHost target = route.getTargetHost();
+ recordCacheMiss(target, request);
+
+ if (!mayCallBackend(request)) {
+ return Proxies.enhanceResponse(
+ new BasicHttpResponse(
+ HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"));
+ }
+
+ Map variants = getExistingCacheVariants(target, request);
+ if (variants != null && variants.size() > 0) {
+ return Proxies.enhanceResponse(
+ negotiateResponseFromVariants(route, request, context, execAware, variants));
+ }
+
+ return callBackend(route, request, context, execAware);
+ }
+
+ private HttpCacheEntry satisfyFromCache(
+ final HttpHost target, final HttpRequestWrapper request) {
+ HttpCacheEntry entry = null;
+ try {
+ entry = responseCache.getCacheEntry(target, request);
+ } catch (IOException ioe) {
+ log.warn("Unable to retrieve entries from cache", ioe);
+ }
+ return entry;
+ }
+
+ private HttpResponse getFatallyNoncompliantResponse(
+ final HttpRequestWrapper request,
+ final HttpContext context) {
+ HttpResponse fatalErrorResponse = null;
+ List fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
+
+ for (RequestProtocolError error : fatalError) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ fatalErrorResponse = requestCompliance.getErrorForRequest(error);
+ }
+ return fatalErrorResponse;
+ }
+
+ private Map getExistingCacheVariants(
+ final HttpHost target,
+ final HttpRequestWrapper request) {
+ Map variants = null;
+ try {
+ variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
+ } catch (IOException ioe) {
+ log.warn("Unable to retrieve variant entries from cache", ioe);
+ }
+ return variants;
+ }
+
+ private void recordCacheMiss(final HttpHost target, final HttpRequestWrapper request) {
+ cacheMisses.getAndIncrement();
+ if (log.isTraceEnabled()) {
+ RequestLine rl = request.getRequestLine();
+ log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
+ }
+ }
+
+ private void recordCacheHit(final HttpHost target, final HttpRequestWrapper request) {
+ cacheHits.getAndIncrement();
+ if (log.isTraceEnabled()) {
+ RequestLine rl = request.getRequestLine();
+ log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
+ }
+ }
+
+ private void recordCacheUpdate(HttpContext context) {
+ cacheUpdates.getAndIncrement();
+ setResponseStatus(context, CacheResponseStatus.VALIDATED);
+ }
+
+ private void flushEntriesInvalidatedByRequest(
+ final HttpHost target,
+ final HttpRequestWrapper request) {
+ try {
+ responseCache.flushInvalidatedCacheEntriesFor(target, request);
+ } catch (IOException ioe) {
+ log.warn("Unable to flush invalidated entries from cache", ioe);
+ }
+ }
+
+ private HttpResponse generateCachedResponse(HttpRequestWrapper request,
+ HttpContext context, HttpCacheEntry entry, Date now) {
+ final HttpResponse cachedResponse;
+ if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
+ || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
+ cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
+ } else {
+ cachedResponse = responseGenerator.generateResponse(entry);
+ }
+ setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+ if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
+ cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
+ }
+ return cachedResponse;
+ }
+
+ private HttpResponse handleRevalidationFailure(
+ final HttpRequestWrapper request,
+ final HttpContext context,
+ final HttpCacheEntry entry,
+ final Date now) {
+ if (staleResponseNotAllowed(request, entry, now)) {
+ return generateGatewayTimeout(context);
+ } else {
+ return unvalidatedCacheHit(context, entry);
+ }
+ }
+
+ private HttpResponse generateGatewayTimeout(final HttpContext context) {
+ setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
+ return new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
+ }
+
+ private HttpResponse unvalidatedCacheHit(
+ final HttpContext context, final HttpCacheEntry entry) {
+ final HttpResponse cachedResponse = responseGenerator.generateResponse(entry);
+ setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
+ cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
+ return cachedResponse;
+ }
+
+ private boolean staleResponseNotAllowed(
+ final HttpRequestWrapper request,
+ final HttpCacheEntry entry,
+ final Date now) {
+ return validityPolicy.mustRevalidate(entry)
+ || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
+ || explicitFreshnessRequest(request, entry, now);
+ }
+
+ private boolean mayCallBackend(final HttpRequestWrapper request) {
+ for (Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
+ for (HeaderElement elt : h.getElements()) {
+ if ("only-if-cached".equals(elt.getName())) {
+ log.trace("Request marked only-if-cached");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean explicitFreshnessRequest(
+ final HttpRequestWrapper request,
+ final HttpCacheEntry entry,
+ final Date now) {
+ for(Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
+ for(HeaderElement elt : h.getElements()) {
+ if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
+ try {
+ int maxstale = Integer.parseInt(elt.getValue());
+ long age = validityPolicy.getCurrentAgeSecs(entry, now);
+ long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
+ if (age - lifetime > maxstale) return true;
+ } catch (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) {
+
+ final ProtocolVersion pv = msg.getProtocolVersion();
+ String existingEntry = viaHeaders.get(pv);
+ if (existingEntry != null) return existingEntry;
+
+ final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
+ final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
+
+ String value;
+ if ("http".equalsIgnoreCase(pv.getProtocol())) {
+ value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(),
+ release);
+ } else {
+ value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), pv.getMajor(),
+ pv.getMinor(), release);
+ }
+ viaHeaders.put(pv, value);
+
+ return value;
+ }
+
+ private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
+ if (context != null) {
+ context.setAttribute(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) {
+ RequestLine line = request.getRequestLine();
+
+ if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod()))
+ return false;
+
+ if (!"*".equals(line.getUri()))
+ return false;
+
+ if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue()))
+ return false;
+
+ return true;
+ }
+
+ CloseableHttpResponse callBackend(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware) throws IOException, HttpException {
+
+ Date requestDate = getCurrentDate();
+
+ log.trace("Calling the backend");
+ CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware);
+ backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+ return handleBackendResponse(route, request, context, execAware,
+ requestDate, getCurrentDate(), backendResponse);
+ }
+
+ private boolean revalidationResponseIsTooOld(HttpResponse backendResponse,
+ HttpCacheEntry cacheEntry) {
+ final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER);
+ final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
+ if (entryDateHeader != null && responseDateHeader != null) {
+ try {
+ Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+ Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
+ if (respDate.before(entryDate)) return true;
+ } catch (DateParseException e) {
+ // 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;
+ }
+
+ HttpResponse negotiateResponseFromVariants(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final Map variants) throws IOException, HttpException {
+ HttpRequestWrapper conditionalRequest = conditionalRequestBuilder
+ .buildConditionalRequestFromVariants(request, variants);
+
+ Date requestDate = getCurrentDate();
+ CloseableHttpResponse backendResponse = backend.execute(
+ route, conditionalRequest, context, execAware);
+ Date responseDate = getCurrentDate();
+
+ backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+
+ if (backendResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
+ return handleBackendResponse(
+ route, request, context, execAware,
+ requestDate, responseDate, backendResponse);
+ }
+
+ Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG);
+ if (resultEtagHeader == null) {
+ log.warn("304 response did not contain ETag");
+ return callBackend(route, request, context, execAware);
+ }
+
+ String resultEtag = resultEtagHeader.getValue();
+ Variant matchingVariant = variants.get(resultEtag);
+ if (matchingVariant == null) {
+ log.debug("304 response did not contain ETag matching one sent in If-None-Match");
+ return callBackend(route, request, context, execAware);
+ }
+
+ HttpCacheEntry matchedEntry = matchingVariant.getEntry();
+
+ if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
+ EntityUtils.consume(backendResponse.getEntity());
+ return retryRequestUnconditionally(route, request, context, execAware, matchedEntry);
+ }
+
+ recordCacheUpdate(context);
+
+ HttpCacheEntry responseEntry = getUpdatedVariantEntry(
+ route.getTargetHost(), conditionalRequest, requestDate, responseDate,
+ backendResponse, matchingVariant, matchedEntry);
+
+ HttpResponse resp = responseGenerator.generateResponse(responseEntry);
+ tryToUpdateVariantMap(route.getTargetHost(), request, matchingVariant);
+
+ if (shouldSendNotModifiedResponse(request, responseEntry)) {
+ return responseGenerator.generateNotModifiedResponse(responseEntry);
+ }
+
+ return resp;
+ }
+
+ private HttpResponse retryRequestUnconditionally(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry matchedEntry) throws IOException, HttpException {
+ HttpRequestWrapper unconditional = conditionalRequestBuilder
+ .buildUnconditionalRequest(request, matchedEntry);
+ return callBackend(route, unconditional, context, execAware);
+ }
+
+ private HttpCacheEntry getUpdatedVariantEntry(
+ final HttpHost target,
+ final HttpRequestWrapper conditionalRequest,
+ final Date requestDate,
+ final Date responseDate,
+ final HttpResponse backendResponse,
+ final Variant matchingVariant,
+ final HttpCacheEntry matchedEntry) {
+ HttpCacheEntry responseEntry = matchedEntry;
+ try {
+ responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest,
+ matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
+ } catch (IOException ioe) {
+ log.warn("Could not update cache entry", ioe);
+ }
+ return responseEntry;
+ }
+
+ private void tryToUpdateVariantMap(
+ final HttpHost target,
+ final HttpRequestWrapper request,
+ final Variant matchingVariant) {
+ try {
+ responseCache.reuseVariantEntryFor(target, request, matchingVariant);
+ } catch (IOException ioe) {
+ log.warn("Could not update cache entry to reuse variant", ioe);
+ }
+ }
+
+ private boolean shouldSendNotModifiedResponse(
+ final HttpRequestWrapper request,
+ final HttpCacheEntry responseEntry) {
+ return (suitabilityChecker.isConditional(request)
+ && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
+ }
+
+ CloseableHttpResponse revalidateCacheEntry(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final HttpCacheEntry cacheEntry) throws IOException, HttpException {
+
+ HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry);
+
+ Date requestDate = getCurrentDate();
+ CloseableHttpResponse backendResponse = backend.execute(
+ route, conditionalRequest, context, execAware);
+ Date responseDate = getCurrentDate();
+
+ if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
+ backendResponse.close();
+ HttpRequestWrapper unconditional = conditionalRequestBuilder
+ .buildUnconditionalRequest(request, cacheEntry);
+ requestDate = getCurrentDate();
+ backendResponse = backend.execute(route, unconditional, context, execAware);
+ responseDate = getCurrentDate();
+ }
+
+ backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
+
+ int statusCode = backendResponse.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
+ recordCacheUpdate(context);
+ }
+
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
+ HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
+ route.getTargetHost(), request, cacheEntry,
+ backendResponse, requestDate, responseDate);
+ if (suitabilityChecker.isConditional(request)
+ && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
+ return Proxies.enhanceResponse(
+ responseGenerator.generateNotModifiedResponse(updatedEntry));
+ }
+ return Proxies.enhanceResponse(responseGenerator.generateResponse(updatedEntry));
+ }
+
+ if (staleIfErrorAppliesTo(statusCode)
+ && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
+ && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
+ try {
+ HttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
+ cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+ return Proxies.enhanceResponse(cachedResponse);
+ } finally {
+ backendResponse.close();
+ }
+ }
+ return handleBackendResponse(
+ route, conditionalRequest, context, execAware,
+ requestDate, responseDate, backendResponse);
+ }
+
+ private boolean staleIfErrorAppliesTo(int statusCode) {
+ return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+ || statusCode == HttpStatus.SC_BAD_GATEWAY
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+ || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
+ }
+
+ CloseableHttpResponse handleBackendResponse(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext context,
+ final HttpExecutionAware execAware,
+ final Date requestDate,
+ final Date responseDate,
+ final CloseableHttpResponse backendResponse) throws IOException {
+
+ log.trace("Handling Backend response");
+ responseCompliance.ensureProtocolCompliance(request, backendResponse);
+
+ HttpHost target = route.getTargetHost();
+ boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
+ responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
+ if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
+ try {
+ return Proxies.enhanceResponse(responseCache.cacheAndReturnResponse(
+ target, request, backendResponse, requestDate, responseDate));
+ } catch (IOException ioe) {
+ log.warn("Unable to store entries in cache", ioe);
+ } finally {
+ backendResponse.close();
+ }
+ }
+ if (!cacheable) {
+ try {
+ responseCache.flushCacheEntriesFor(target, request);
+ } catch (IOException ioe) {
+ log.warn("Unable to flush invalid cache entries", ioe);
+ }
+ }
+ return backendResponse;
+ }
+
+ private boolean alreadyHaveNewerCacheEntry(HttpHost target, HttpRequestWrapper request,
+ HttpResponse backendResponse) {
+ HttpCacheEntry existing = null;
+ try {
+ existing = responseCache.getCacheEntry(target, request);
+ } catch (IOException ioe) {
+ // nop
+ }
+ if (existing == null) return false;
+ Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER);
+ if (entryDateHeader == null) return false;
+ Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
+ if (responseDateHeader == null) return false;
+ try {
+ Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
+ Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
+ return responseDate.before(entryDate);
+ } catch (DateParseException e) {
+ // Empty on Purpose
+ }
+ return false;
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
index d722a0f37..f2bdc05aa 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
@@ -29,10 +29,18 @@ package org.apache.http.impl.client.cache;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.URI;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
@@ -109,7 +117,10 @@ import org.apache.http.util.VersionInfo;
* memcached} storage backends.
*
* @since 4.1
+ *
+ * @deprecated (4.3)
*/
+@Deprecated
@ThreadSafe // So long as the responseCache implementation is threadsafe
public class CachingHttpClient implements HttpClient {
@@ -960,4 +971,151 @@ public class CachingHttpClient implements HttpClient {
return false;
}
+ static class AsynchronousValidator {
+ private final CachingHttpClient cachingClient;
+ private final ExecutorService executor;
+ private final Set queued;
+ private final CacheKeyGenerator cacheKeyGenerator;
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /**
+ * Create AsynchronousValidator which will make revalidation requests
+ * using the supplied {@link CachingHttpClient}, and
+ * a {@link ThreadPoolExecutor} generated according to the thread
+ * pool settings provided in the given {@link CacheConfig}.
+ * @param cachingClient used to execute asynchronous requests
+ * @param config specifies thread pool settings. See
+ * {@link CacheConfig#getAsynchronousWorkersMax()},
+ * {@link CacheConfig#getAsynchronousWorkersCore()},
+ * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
+ * and {@link CacheConfig#getRevalidationQueueSize()}.
+ */
+ public AsynchronousValidator(CachingHttpClient cachingClient,
+ CacheConfig config) {
+ this(cachingClient,
+ new ThreadPoolExecutor(config.getAsynchronousWorkersCore(),
+ config.getAsynchronousWorkersMax(),
+ config.getAsynchronousWorkerIdleLifetimeSecs(),
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue(config.getRevalidationQueueSize()))
+ );
+ }
+
+ /**
+ * Create AsynchronousValidator which will make revalidation requests
+ * using the supplied {@link CachingHttpClient} and
+ * {@link ExecutorService}.
+ * @param cachingClient used to execute asynchronous requests
+ * @param executor used to manage a thread pool of revalidation workers
+ */
+ AsynchronousValidator(CachingHttpClient cachingClient,
+ ExecutorService executor) {
+ this.cachingClient = cachingClient;
+ this.executor = executor;
+ this.queued = new HashSet();
+ this.cacheKeyGenerator = new CacheKeyGenerator();
+ }
+
+ /**
+ * Schedules an asynchronous revalidation
+ *
+ * @param target
+ * @param request
+ * @param context
+ * @param entry
+ */
+ public synchronized void revalidateCacheEntry(HttpHost target,
+ HttpRequestWrapper request, HttpContext context, HttpCacheEntry entry) {
+ // getVariantURI will fall back on getURI if no variants exist
+ String uri = cacheKeyGenerator.getVariantURI(target, request, entry);
+
+ if (!queued.contains(uri)) {
+ AsynchronousValidationRequest revalidationRequest =
+ new AsynchronousValidationRequest(this, cachingClient, target,
+ request, context, entry, uri);
+
+ try {
+ executor.execute(revalidationRequest);
+ queued.add(uri);
+ } catch (RejectedExecutionException ree) {
+ log.debug("Revalidation for [" + uri + "] not scheduled: " + ree);
+ }
+ }
+ }
+
+ /**
+ * Removes an identifier from the internal list of revalidation jobs in
+ * progress. This is meant to be called by
+ * {@link AsynchronousValidationRequest#run()} once the revalidation is
+ * complete, using the identifier passed in during constructions.
+ * @param identifier
+ */
+ synchronized void markComplete(String identifier) {
+ queued.remove(identifier);
+ }
+
+ Set getScheduledIdentifiers() {
+ return Collections.unmodifiableSet(queued);
+ }
+
+ ExecutorService getExecutor() {
+ return executor;
+ }
+ }
+
+ static class AsynchronousValidationRequest implements Runnable {
+ private final AsynchronousValidator parent;
+ private final CachingHttpClient cachingClient;
+ private final HttpHost target;
+ private final HttpRequestWrapper request;
+ private final HttpContext context;
+ private final HttpCacheEntry cacheEntry;
+ private final String identifier;
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /**
+ * Used internally by {@link AsynchronousValidator} to schedule a
+ * revalidation.
+ * @param cachingClient
+ * @param target
+ * @param request
+ * @param context
+ * @param cacheEntry
+ * @param bookKeeping
+ * @param identifier
+ */
+ AsynchronousValidationRequest(AsynchronousValidator parent,
+ CachingHttpClient cachingClient, HttpHost target,
+ HttpRequestWrapper request, HttpContext context,
+ HttpCacheEntry cacheEntry,
+ String identifier) {
+ this.parent = parent;
+ this.cachingClient = cachingClient;
+ this.target = target;
+ this.request = request;
+ this.context = context;
+ this.cacheEntry = cacheEntry;
+ this.identifier = identifier;
+ }
+
+ public void run() {
+ try {
+ cachingClient.revalidateCacheEntry(target, request, context, cacheEntry);
+ } catch (IOException ioe) {
+ log.debug("Asynchronous revalidation failed due to exception: " + ioe);
+ } catch (ProtocolException pe) {
+ log.error("ProtocolException thrown during asynchronous revalidation: " + pe);
+ } finally {
+ parent.markComplete(identifier);
+ }
+ }
+
+ String getIdentifier() {
+ return identifier;
+ }
+
+ }
+
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java
new file mode 100644
index 000000000..7ce19e006
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java
@@ -0,0 +1,30 @@
+package org.apache.http.impl.client.cache;
+
+import java.lang.reflect.Proxy;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.util.Args;
+
+/**
+ * Proxies for HTTP message objects.
+ *
+ * @since 4.3
+ */
+@NotThreadSafe
+class Proxies {
+
+ public static CloseableHttpResponse enhanceResponse(final HttpResponse original) {
+ Args.notNull(original, "HTTP response");
+ if (original instanceof CloseableHttpResponse) {
+ return (CloseableHttpResponse) original;
+ } else {
+ return (CloseableHttpResponse) Proxy.newProxyInstance(
+ ResponseProxyHandler.class.getClassLoader(),
+ new Class>[] { CloseableHttpResponse.class },
+ new ResponseProxyHandler(original));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java
new file mode 100644
index 000000000..3767c6821
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java
@@ -0,0 +1,89 @@
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+
+package org.apache.http.impl.client.cache;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * A proxy class that can enhance an arbitrary {@link HttpResponse} with
+ * {@link Closeable#close()} method.
+ *
+ * @since 4.3
+ */
+@NotThreadSafe
+class ResponseProxyHandler implements InvocationHandler {
+
+ private static final Method CLOSE_METHOD;
+
+ static {
+ try {
+ CLOSE_METHOD = Closeable.class.getMethod("close");
+ } catch (NoSuchMethodException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private final HttpResponse original;
+
+ ResponseProxyHandler(final HttpResponse original) {
+ super();
+ this.original = original;
+ }
+
+ public void close() throws IOException {
+ EntityUtils.consume(original.getEntity());
+ }
+
+ public Object invoke(
+ final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (method.equals(CLOSE_METHOD)) {
+ close();
+ return null;
+ } else {
+ try {
+ return method.invoke(this.original, args);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause != null) {
+ throw cause;
+ } else {
+ throw ex;
+ }
+ }
+ }
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
index 6fb0d2c14..be664f9af 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
@@ -43,7 +43,6 @@ import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.impl.client.cache.CacheConfig;
-import org.apache.http.impl.client.cache.CachingHttpClient;
/**
*
This class is a storage backend that uses an external memcached
@@ -62,13 +61,13 @@ import org.apache.http.impl.client.cache.CachingHttpClient;
* fails (see the
* KetamaConnectionFactory).
*
- *
+ *
*
Because memcached places limits on the size of its keys, we need to
* introduce a key hashing scheme to map the annotated URLs the higher-level
- * {@link CachingHttpClient} wants to use as keys onto ones that are suitable
+ * caching HTTP client wants to use as keys onto ones that are suitable
* for use with memcached. Please see {@link KeyHashingScheme} if you would
* like to use something other than the provided {@link SHA256KeyHashingScheme}.
- *
+ *
*
Because this hashing scheme can potentially result in key collisions (though
* highly unlikely), we need to store the higher-level logical storage key along
* with the {@link HttpCacheEntry} so that we can re-check it on retrieval. There
@@ -87,7 +86,7 @@ import org.apache.http.impl.client.cache.CachingHttpClient;
public class MemcachedHttpCacheStorage implements HttpCacheStorage {
private static final Log log = LogFactory.getLog(MemcachedHttpCacheStorage.class);
-
+
private final MemcachedClientIF client;
private final KeyHashingScheme keyHashingScheme;
private final MemcachedCacheEntryFactory memcachedCacheEntryFactory;
@@ -158,7 +157,7 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
this.memcachedCacheEntryFactory = memcachedCacheEntryFactory;
this.keyHashingScheme = keyHashingScheme;
}
-
+
public void putEntry(String url, HttpCacheEntry entry) throws IOException {
byte[] bytes = serializeEntry(url, entry);
String key = getCacheKey(url);
@@ -209,7 +208,7 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
}
return mce;
}
-
+
public HttpCacheEntry getEntry(String url) throws IOException {
String key = getCacheKey(url);
if (key == null) return null;
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
index 20b611f58..e353f764a 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
@@ -27,15 +27,19 @@
package org.apache.http.impl.client.cache;
import java.util.HashMap;
+
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
-import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.client.execchain.ClientExecChain;
import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.protocol.HttpContext;
import org.easymock.IExpectationSetters;
import org.easymock.classextension.EasyMock;
import org.junit.Before;
@@ -46,16 +50,17 @@ public abstract class AbstractProtocolTest {
protected static final int MAX_ENTRIES = 100;
protected int entityLength = 128;
protected HttpHost host;
+ protected HttpRoute route;
protected HttpEntity body;
- protected HttpClient mockBackend;
+ protected ClientExecChain mockBackend;
protected HttpCache mockCache;
protected HttpRequestWrapper request;
- protected HttpResponse originResponse;
+ protected CloseableHttpResponse originResponse;
protected CacheConfig config;
- protected CachingHttpClient impl;
+ protected CachingExec impl;
protected HttpCache cache;
- public static HttpRequest eqRequest(HttpRequest in) {
+ public static HttpRequestWrapper eqRequest(HttpRequestWrapper in) {
EasyMock.reportMatcher(new RequestEquivalent(in));
return null;
}
@@ -64,20 +69,23 @@ public abstract class AbstractProtocolTest {
public void setUp() {
host = new HttpHost("foo.example.com");
+ route = new HttpRoute(host);
+
body = HttpTestUtils.makeBody(entityLength);
request = HttpRequestWrapper.wrap(new BasicHttpRequest("GET", "/foo", HttpVersion.HTTP_1_1));
- originResponse = HttpTestUtils.make200Response();
+ originResponse = Proxies.enhanceResponse(HttpTestUtils.make200Response());
config = CacheConfig.custom()
.setMaxCacheEntries(MAX_ENTRIES)
.setMaxObjectSize(MAX_BYTES)
.build();
+
cache = new BasicHttpCache(config);
- mockBackend = EasyMock.createNiceMock(HttpClient.class);
+ mockBackend = EasyMock.createNiceMock(ClientExecChain.class);
mockCache = EasyMock.createNiceMock(HttpCache.class);
- impl = new CachingHttpClient(mockBackend, cache, config);
+ impl = new CachingExec(mockBackend, cache, config);
}
protected void replayMocks() {
@@ -90,17 +98,30 @@ public abstract class AbstractProtocolTest {
EasyMock.verify(mockCache);
}
- protected IExpectationSetters backendExpectsAnyRequest() throws Exception {
- HttpResponse resp = mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock
- .isA(HttpRequest.class), (HttpContext) EasyMock.isNull());
+ protected IExpectationSetters backendExpectsAnyRequest() throws Exception {
+ CloseableHttpResponse resp = mockBackend.execute(
+ EasyMock.isA(HttpRoute.class),
+ EasyMock.isA(HttpRequestWrapper.class),
+ EasyMock.isA(HttpClientContext.class),
+ EasyMock.isNull());
return EasyMock.expect(resp);
}
+ protected IExpectationSetters backendExpectsAnyRequestAndReturn(
+ HttpResponse reponse) throws Exception {
+ CloseableHttpResponse resp = mockBackend.execute(
+ EasyMock.isA(HttpRoute.class),
+ EasyMock.isA(HttpRequestWrapper.class),
+ EasyMock.isA(HttpClientContext.class),
+ EasyMock.isNull());
+ return EasyMock.expect(resp).andReturn(Proxies.enhanceResponse(reponse));
+ }
+
protected void emptyMockCacheExpectsNoPuts() throws Exception {
- mockBackend = EasyMock.createNiceMock(HttpClient.class);
+ mockBackend = EasyMock.createNiceMock(ClientExecChain.class);
mockCache = EasyMock.createNiceMock(HttpCache.class);
- impl = new CachingHttpClient(mockBackend, mockCache, config);
+ impl = new CachingExec(mockBackend, mockCache, config);
EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
.andReturn(null).anyTimes();
@@ -126,7 +147,7 @@ public abstract class AbstractProtocolTest {
.setMaxObjectSize(MAX_BYTES)
.setSharedCache(false)
.build();
- impl = new CachingHttpClient(mockBackend, cache, config);
+ impl = new CachingExec(mockBackend, cache, config);
}
public AbstractProtocolTest() {
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
deleted file mode 100644
index 263be6644..000000000
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * ====================================================================
- * 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
- * .
- *
- */
-package org.apache.http.impl.client.cache;
-
-import java.util.Date;
-import java.util.Random;
-
-import org.apache.http.Header;
-import org.apache.http.HeaderElement;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.cookie.DateUtils;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.message.BasicHttpResponse;
-import org.apache.http.protocol.HttpContext;
-import org.easymock.classextension.EasyMock;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class DoNotTestProtocolRequirements {
-
- private static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
-
- private static final int MAX_BYTES = 1024;
- private static final int MAX_ENTRIES = 100;
-
- private HttpHost host;
- private HttpEntity mockEntity;
- private HttpClient mockBackend;
- private HttpCache mockCache;
- private HttpRequest request;
- private HttpResponse originResponse;
-
- private CachingHttpClient impl;
-
- @Before
- public void setUp() {
- host = new HttpHost("foo.example.com");
-
- request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
-
- originResponse = make200Response();
- CacheConfig params = CacheConfig.custom()
- .setMaxObjectSize(MAX_BYTES)
- .setMaxCacheEntries(MAX_ENTRIES).build();
-
- HttpCache cache = new BasicHttpCache(params);
- mockBackend = EasyMock.createNiceMock(HttpClient.class);
- mockEntity = EasyMock.createNiceMock(HttpEntity.class);
- mockCache = EasyMock.createNiceMock(HttpCache.class);
- impl = new CachingHttpClient(mockBackend, cache, params);
- }
-
- private HttpResponse make200Response() {
- HttpResponse out = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK");
- out.setHeader("Date", DateUtils.formatDate(new Date()));
- out.setHeader("Server", "MockOrigin/1.0");
- out.setEntity(makeBody(128));
- return out;
- }
-
- private void replayMocks() {
- EasyMock.replay(mockBackend);
- EasyMock.replay(mockCache);
- EasyMock.replay(mockEntity);
- }
-
- private HttpEntity makeBody(int nbytes) {
- byte[] bytes = new byte[nbytes];
- (new Random()).nextBytes(bytes);
- return new ByteArrayEntity(bytes);
- }
-
- /*
- * "10.2.7 206 Partial Content ... The response MUST include the following
- * header fields:
- *
- * - Either a Content-Range header field (section 14.16) indicating the
- * range included with this response, or a multipart/byteranges Content-Type
- * including Content-Range fields for each part. If a Content-Length header
- * field is present in the response, its value MUST match the actual number
- * of OCTETs transmitted in the message-body.
- *
- * - Date
- *
- * - ETag and/or Content-Location, if the header would have been sent in a
- * 200 response to the same request
- *
- * - Expires, Cache-Control, and/or Vary, if the field-value might differ
- * from that sent in any previous response for the same variant
- *
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7
- */
- @Test
- @Ignore
- public void test206ResponseReturnedToClientMustHaveContentRangeOrByteRangesContentType()
- throws Exception {
- request.addHeader("Range", "bytes 0-499/1234");
- originResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
- "Partial Content");
- originResponse.setHeader("Date", DateUtils.formatDate(new Date()));
- originResponse.setHeader("Server", "MockOrigin/1.0");
- originResponse.setEntity(makeBody(500));
-
- org.easymock.EasyMock.expect(
- mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
- org.easymock.EasyMock.isA(HttpRequest.class),
- (HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
-
- replayMocks();
-
- try {
- HttpResponse result = impl.execute(host, request);
- Header crHdr = result.getFirstHeader("Content-Range");
- Header ctHdr = result.getFirstHeader("Content-Type");
- if (result.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT) {
- if (crHdr == null) {
- Assert.assertNotNull(ctHdr);
- boolean foundMultipartByteRanges = false;
- for (HeaderElement elt : ctHdr.getElements()) {
- if ("multipart/byteranges".equalsIgnoreCase(elt.getName())) {
- NameValuePair param = elt.getParameterByName("boundary");
- Assert.assertNotNull(param);
- String boundary = param.getValue();
- Assert.assertNotNull(boundary);
- // perhaps eventually should parse out the
- // request body to check proper formatting
- // but that might be excessive; HttpClient
- // developers have indicated that
- // HttpClient's job does not involve
- // parsing a response body
- }
- }
- Assert.assertTrue(foundMultipartByteRanges);
- }
- }
- } catch (ClientProtocolException acceptableBehavior) {
- }
- }
-
- @Test
- @Ignore
- public void test206ResponseReturnedToClientWithAContentLengthMustMatchActualOctetsTransmitted() {
- // We are explicitly saying that CachingHttpClient does not care about
- // this:
- // We do not attempt to cache 206, nor do we ever construct a 206. We
- // simply pass along a 206,
- // which could be malformed. But protocol compliance of a downstream
- // server is not our responsibility
- }
-
-}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java
new file mode 100644
index 000000000..ef3562c10
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java
@@ -0,0 +1,70 @@
+/*
+ * ====================================================================
+ *
+ * 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
+ * .
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpExecutionAware;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.client.execchain.ClientExecChain;
+import org.apache.http.message.BasicHttpResponse;
+
+public class DummyBackend implements ClientExecChain {
+
+ private HttpRequest request;
+ private HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
+ private int executions = 0;
+
+ public void setResponse(HttpResponse resp) {
+ response = resp;
+ }
+
+ public HttpRequest getCapturedRequest() {
+ return request;
+ }
+
+ public CloseableHttpResponse execute(
+ final HttpRoute route,
+ final HttpRequestWrapper request,
+ final HttpClientContext clientContext,
+ final HttpExecutionAware execAware) throws IOException, HttpException {
+ this.request = request;
+ executions++;
+ return Proxies.enhanceResponse(response);
+ }
+
+ public int getExecutions() {
+ return executions;
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java
deleted file mode 100644
index b829c6bcc..000000000
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * ====================================================================
- *
- * 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
- * .
- *
- */
-package org.apache.http.impl.client.cache;
-
-import java.io.IOException;
-
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.impl.conn.SingleClientConnManager;
-import org.apache.http.message.BasicHttpResponse;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-
-@SuppressWarnings("deprecation")
-public class DummyHttpClient implements HttpClient {
-
- private HttpParams params = new BasicHttpParams();
- private ClientConnectionManager connManager = new SingleClientConnManager();
- private HttpRequest request;
- private HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
- private int executions = 0;
-
- public void setParams(HttpParams params) {
- this.params = params;
- }
-
- public HttpParams getParams() {
- return params;
- }
-
- public ClientConnectionManager getConnectionManager() {
- return connManager;
- }
-
- public void setConnectionManager(ClientConnectionManager ccm) {
- connManager = ccm;
- }
-
- public void setResponse(HttpResponse resp) {
- response = resp;
- }
-
- public HttpRequest getCapturedRequest() {
- return request;
- }
-
- public HttpResponse execute(HttpUriRequest request) throws IOException,
- ClientProtocolException {
- this.request = request;
- executions++;
- return response;
- }
-
- public HttpResponse execute(HttpUriRequest request, HttpContext context)
- throws IOException, ClientProtocolException {
- this.request = request;
- executions++;
- return response;
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request)
- throws IOException, ClientProtocolException {
- this.request = request;
- executions++;
- return response;
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request,
- HttpContext context) throws IOException, ClientProtocolException {
- this.request = request;
- executions++;
- return response;
- }
-
- public T execute(HttpUriRequest request,
- ResponseHandler extends T> responseHandler) throws IOException,
- ClientProtocolException {
- this.request = request;
- executions++;
- return responseHandler.handleResponse(response);
- }
-
- public T execute(HttpUriRequest request,
- ResponseHandler extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- this.request = request;
- executions++;
- return responseHandler.handleResponse(response);
- }
-
- public T execute(HttpHost target, HttpRequest request,
- ResponseHandler extends T> responseHandler) throws IOException,
- ClientProtocolException {
- this.request = request;
- executions++;
- return responseHandler.handleResponse(response);
- }
-
- public T execute(HttpHost target, HttpRequest request,
- ResponseHandler extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- this.request = request;
- executions++;
- return responseHandler.handleResponse(response);
- }
-
- public int getExecutions() {
- return executions;
- }
-}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
index 41e5c6fa8..fe186092e 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
@@ -31,9 +31,11 @@ import java.io.IOException;
import org.apache.http.HttpHost;
import org.apache.http.ProtocolException;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestWrapper;
-import org.apache.http.protocol.HttpContext;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
import org.easymock.classextension.EasyMock;
import org.junit.Before;
import org.junit.Test;
@@ -41,51 +43,36 @@ import org.junit.Test;
public class TestAsynchronousValidationRequest {
private AsynchronousValidator mockParent;
- private CachingHttpClient mockClient;
- private HttpHost target;
+ private CachingExec mockClient;
+ private HttpRoute route;
private HttpRequestWrapper request;
- private HttpContext mockContext;
+ private HttpClientContext context;
+ private HttpExecutionAware mockExecAware;
private HttpCacheEntry mockCacheEntry;
@Before
public void setUp() {
mockParent = EasyMock.createNiceMock(AsynchronousValidator.class);
- mockClient = EasyMock.createNiceMock(CachingHttpClient.class);
- target = new HttpHost("foo.example.com");
+ mockClient = EasyMock.createNiceMock(CachingExec.class);
+ route = new HttpRoute(new HttpHost("foo.example.com"));
request = HttpRequestWrapper.wrap(new HttpGet("/"));
- mockContext = EasyMock.createNiceMock(HttpContext.class);
+ context = HttpClientContext.create();
+ mockExecAware = EasyMock.createNiceMock(HttpExecutionAware.class);
mockCacheEntry = EasyMock.createNiceMock(HttpCacheEntry.class);
}
@Test
- public void testRunCallsCachingClientAndRemovesIdentifier() throws ProtocolException, IOException {
- String identifier = "foo";
-
- AsynchronousValidationRequest asynchRequest = new AsynchronousValidationRequest(
- mockParent, mockClient, target, request, mockContext, mockCacheEntry,
- identifier);
-
- // response not used
- EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
- mockParent.markComplete(identifier);
-
- replayMocks();
- asynchRequest.run();
- verifyMocks();
- }
-
- @Test
- public void testRunGracefullyHandlesProtocolException() throws IOException, ProtocolException {
+ public void testRunCallsCachingClientAndRemovesIdentifier() throws Exception {
String identifier = "foo";
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, target, request, mockContext, mockCacheEntry,
+ mockParent, mockClient, route, request, context, mockExecAware, mockCacheEntry,
identifier);
// response not used
EasyMock.expect(
- mockClient.revalidateCacheEntry(target, request, mockContext,
- mockCacheEntry)).andThrow(new ProtocolException());
+ mockClient.revalidateCacheEntry(
+ route, request, context, mockExecAware, mockCacheEntry)).andReturn(null);
mockParent.markComplete(identifier);
replayMocks();
@@ -94,17 +81,38 @@ public class TestAsynchronousValidationRequest {
}
@Test
- public void testRunGracefullyHandlesIOException() throws IOException, ProtocolException {
+ public void testRunGracefullyHandlesProtocolException() throws Exception {
String identifier = "foo";
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
- mockParent, mockClient, target, request, mockContext, mockCacheEntry,
+ mockParent, mockClient, route, request, context, mockExecAware, mockCacheEntry,
identifier);
// response not used
EasyMock.expect(
- mockClient.revalidateCacheEntry(target, request, mockContext,
- mockCacheEntry)).andThrow(new IOException());
+ mockClient.revalidateCacheEntry(
+ route, request, context, mockExecAware, mockCacheEntry)).andThrow(
+ new ProtocolException());
+ mockParent.markComplete(identifier);
+
+ replayMocks();
+ impl.run();
+ verifyMocks();
+ }
+
+ @Test
+ public void testRunGracefullyHandlesIOException() throws Exception {
+ String identifier = "foo";
+
+ AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
+ mockParent, mockClient, route, request, context, mockExecAware, mockCacheEntry,
+ identifier);
+
+ // response not used
+ EasyMock.expect(
+ mockClient.revalidateCacheEntry(
+ route, request, context, mockExecAware, mockCacheEntry)).andThrow(
+ new IOException());
mockParent.markComplete(identifier);
replayMocks();
@@ -114,13 +122,13 @@ public class TestAsynchronousValidationRequest {
public void replayMocks() {
EasyMock.replay(mockClient);
- EasyMock.replay(mockContext);
+ EasyMock.replay(mockExecAware);
EasyMock.replay(mockCacheEntry);
}
public void verifyMocks() {
EasyMock.verify(mockClient);
- EasyMock.verify(mockContext);
+ EasyMock.verify(mockExecAware);
EasyMock.verify(mockCacheEntry);
}
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
index 67e280425..b860ab7a4 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
@@ -26,7 +26,6 @@
*/
package org.apache.http.impl.client.cache;
-import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
@@ -36,13 +35,14 @@ import junit.framework.Assert;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
-import org.apache.http.ProtocolException;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.message.BasicHeader;
-import org.apache.http.protocol.HttpContext;
import org.easymock.Capture;
import org.easymock.classextension.EasyMock;
import org.junit.Before;
@@ -52,22 +52,23 @@ public class TestAsynchronousValidator {
private AsynchronousValidator impl;
- private CachingHttpClient mockClient;
- private HttpHost target;
+ private CachingExec mockClient;
+ private HttpRoute route;
private HttpRequestWrapper request;
- private HttpContext mockContext;
+ private HttpClientContext context;
+ private HttpExecutionAware mockExecAware;
private HttpCacheEntry mockCacheEntry;
private ExecutorService mockExecutor;
@Before
public void setUp() {
- mockClient = EasyMock.createNiceMock(CachingHttpClient.class);
- target = new HttpHost("foo.example.com");
+ mockClient = EasyMock.createNiceMock(CachingExec.class);
+ route = new HttpRoute(new HttpHost("foo.example.com"));
request = HttpRequestWrapper.wrap(new HttpGet("/"));
- mockContext = EasyMock.createNiceMock(HttpContext.class);
+ context = HttpClientContext.create();
+ mockExecAware = EasyMock.createNiceMock(HttpExecutionAware.class);
mockCacheEntry = EasyMock.createNiceMock(HttpCacheEntry.class);
-
mockExecutor = EasyMock.createNiceMock(ExecutorService.class);
}
@@ -80,7 +81,7 @@ public class TestAsynchronousValidator {
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
replayMocks();
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@@ -95,7 +96,7 @@ public class TestAsynchronousValidator {
mockExecutor.execute(EasyMock.capture(cap));
replayMocks();
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@@ -114,7 +115,7 @@ public class TestAsynchronousValidator {
EasyMock.expectLastCall().andThrow(new RejectedExecutionException());
replayMocks();
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
@@ -130,8 +131,8 @@ public class TestAsynchronousValidator {
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
replayMocks();
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
@@ -157,8 +158,8 @@ public class TestAsynchronousValidator {
EasyMock.expectLastCall().times(2);
replayMocks();
- impl.revalidateCacheEntry(target, HttpRequestWrapper.wrap(req1), mockContext, mockCacheEntry);
- impl.revalidateCacheEntry(target, HttpRequestWrapper.wrap(req2), mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, HttpRequestWrapper.wrap(req1), context, mockExecAware, mockCacheEntry);
+ impl.revalidateCacheEntry(route, HttpRequestWrapper.wrap(req2), context, mockExecAware, mockCacheEntry);
verifyMocks();
Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
@@ -166,7 +167,7 @@ public class TestAsynchronousValidator {
}
@Test
- public void testRevalidateCacheEntryEndToEnd() throws ProtocolException, IOException {
+ public void testRevalidateCacheEntryEndToEnd() throws Exception {
CacheConfig config = CacheConfig.custom()
.setAsynchronousWorkersMax(1)
.setAsynchronousWorkersCore(1)
@@ -174,10 +175,11 @@ public class TestAsynchronousValidator {
impl = new AsynchronousValidator(mockClient, config);
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
- EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
+ EasyMock.expect(mockClient.revalidateCacheEntry(
+ route, request, context, mockExecAware, mockCacheEntry)).andReturn(null);
replayMocks();
- impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
+ impl.revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
try {
// shut down backend executor and make sure all finishes properly, 1 second should be sufficient
@@ -196,14 +198,14 @@ public class TestAsynchronousValidator {
public void replayMocks() {
EasyMock.replay(mockExecutor);
EasyMock.replay(mockClient);
- EasyMock.replay(mockContext);
+ EasyMock.replay(mockExecAware);
EasyMock.replay(mockCacheEntry);
}
public void verifyMocks() {
EasyMock.verify(mockExecutor);
EasyMock.verify(mockClient);
- EasyMock.verify(mockContext);
+ EasyMock.verify(mockExecAware);
EasyMock.verify(mockCacheEntry);
}
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExec.java
similarity index 53%
rename from httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
rename to httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExec.java
index 391866d98..518153eaf 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExec.java
@@ -27,12 +27,12 @@
package org.apache.http.impl.client.cache;
import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.isNull;
import static org.easymock.EasyMock.same;
+import static org.easymock.EasyMock.eq;
import static org.easymock.classextension.EasyMock.createMockBuilder;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
@@ -42,9 +42,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -52,38 +50,38 @@ import java.util.List;
import java.util.Map;
import org.apache.http.Header;
-import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
-import org.apache.http.ProtocolException;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.client.execchain.ClientExecChain;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
-import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
-import org.apache.http.protocol.HttpContext;
import org.easymock.Capture;
+import org.easymock.IExpectationSetters;
+import org.easymock.classextension.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-public class TestCachingHttpClient {
+public class TestCachingExec {
private static final String GET_CURRENT_DATE = "getCurrentDate";
@@ -93,78 +91,80 @@ public class TestCachingHttpClient {
private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
- private CachingHttpClient impl;
+ private CachingExec impl;
private boolean mockedImpl;
private CacheValidityPolicy mockValidityPolicy;
private CacheableRequestPolicy mockRequestPolicy;
- private HttpClient mockBackend;
+ private ClientExecChain mockBackend;
private HttpCache mockCache;
private HttpCacheStorage mockStorage;
private CachedResponseSuitabilityChecker mockSuitabilityChecker;
private ResponseCachingPolicy mockResponsePolicy;
- private HttpResponse mockBackendResponse;
+ private CloseableHttpResponse mockBackendResponse;
private HttpCacheEntry mockCacheEntry;
private CachedHttpResponseGenerator mockResponseGenerator;
- private ClientConnectionManager mockConnectionManager;
private ResponseHandler