Ported CachingHttpClient to ClientExecChain API
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1418743 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
dd3d12f0d6
commit
fcd02a0a18
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String>();
|
||||
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);
|
||||
|
|
830
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
vendored
Normal file
830
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
vendored
Normal file
|
@ -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
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
* </p>
|
||||
*
|
||||
* @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<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(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<String, Variant> 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<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
|
||||
|
||||
for (RequestProtocolError error : fatalError) {
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
|
||||
fatalErrorResponse = requestCompliance.getErrorForRequest(error);
|
||||
}
|
||||
return fatalErrorResponse;
|
||||
}
|
||||
|
||||
private Map<String, Variant> getExistingCacheVariants(
|
||||
final HttpHost target,
|
||||
final HttpRequestWrapper request) {
|
||||
Map<String,Variant> 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<String, Variant> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.</p>
|
||||
* </p>
|
||||
* @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<String> 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<Runnable>(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<String>();
|
||||
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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
30
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java
vendored
Normal file
30
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Proxies.java
vendored
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
89
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java
vendored
Normal file
89
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProxyHandler.java
vendored
Normal file
|
@ -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
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>This class is a storage backend that uses an external <i>memcached</i>
|
||||
|
@ -62,13 +61,13 @@ import org.apache.http.impl.client.cache.CachingHttpClient;
|
|||
* fails (see the <a href="http://dustin.github.com/java-memcached-client/apidocs/net/spy/memcached/KetamaConnectionFactory.html">
|
||||
* KetamaConnectionFactory</a>).
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <p>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}.</p>
|
||||
*
|
||||
*
|
||||
* <p>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;
|
||||
|
|
|
@ -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<HttpResponse> backendExpectsAnyRequest() throws Exception {
|
||||
HttpResponse resp = mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock
|
||||
.isA(HttpRequest.class), (HttpContext) EasyMock.isNull());
|
||||
protected IExpectationSetters<CloseableHttpResponse> backendExpectsAnyRequest() throws Exception {
|
||||
CloseableHttpResponse resp = mockBackend.execute(
|
||||
EasyMock.isA(HttpRoute.class),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull());
|
||||
return EasyMock.expect(resp);
|
||||
}
|
||||
|
||||
protected IExpectationSetters<CloseableHttpResponse> backendExpectsAnyRequestAndReturn(
|
||||
HttpResponse reponse) throws Exception {
|
||||
CloseableHttpResponse resp = mockBackend.execute(
|
||||
EasyMock.isA(HttpRoute.class),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>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() {
|
||||
|
|
|
@ -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
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
}
|
70
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java
vendored
Normal file
70
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyBackend.java
vendored
Normal file
|
@ -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
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
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> T execute(HttpUriRequest request,
|
||||
ResponseHandler<? extends T> responseHandler) throws IOException,
|
||||
ClientProtocolException {
|
||||
this.request = request;
|
||||
executions++;
|
||||
return responseHandler.handleResponse(response);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpUriRequest request,
|
||||
ResponseHandler<? extends T> responseHandler, HttpContext context)
|
||||
throws IOException, ClientProtocolException {
|
||||
this.request = request;
|
||||
executions++;
|
||||
return responseHandler.handleResponse(response);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpHost target, HttpRequest request,
|
||||
ResponseHandler<? extends T> responseHandler) throws IOException,
|
||||
ClientProtocolException {
|
||||
this.request = request;
|
||||
executions++;
|
||||
return responseHandler.handleResponse(response);
|
||||
}
|
||||
|
||||
public <T> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,21 +32,18 @@ import java.util.Date;
|
|||
import junit.framework.Assert;
|
||||
|
||||
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.cache.HttpCacheStorage;
|
||||
import org.apache.http.client.cache.ResourceFactory;
|
||||
import org.apache.http.client.methods.HttpExecutionAware;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.cache.CacheConfig;
|
||||
import org.apache.http.impl.client.cache.CachingHttpClient;
|
||||
import org.apache.http.impl.client.cache.FileResourceFactory;
|
||||
import org.apache.http.impl.client.cache.ManagedHttpCacheStorage;
|
||||
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.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.protocol.BasicHttpContext;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.easymock.EasyMock;
|
||||
import org.junit.After;
|
||||
|
@ -92,10 +89,11 @@ public class TestHttpCacheJiraNumber1147 {
|
|||
ResourceFactory resourceFactory = new FileResourceFactory(cacheDir);
|
||||
HttpCacheStorage httpCacheStorage = new ManagedHttpCacheStorage(cacheConfig);
|
||||
|
||||
HttpClient client = EasyMock.createNiceMock(HttpClient.class);
|
||||
HttpGet get = new HttpGet("http://somehost/");
|
||||
HttpContext context = new BasicHttpContext();
|
||||
ClientExecChain backend = EasyMock.createNiceMock(ClientExecChain.class);
|
||||
HttpRequestWrapper get = HttpRequestWrapper.wrap(new HttpGet("http://somehost/"));
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
HttpHost target = new HttpHost("somehost");
|
||||
HttpRoute route = new HttpRoute(target);
|
||||
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
@ -107,34 +105,37 @@ public class TestHttpCacheJiraNumber1147 {
|
|||
response.setHeader("Cache-Control", "public, max-age=3600");
|
||||
response.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
EasyMock.expect(client.execute(
|
||||
EasyMock.eq(target),
|
||||
EasyMock.isA(HttpRequest.class),
|
||||
EasyMock.same(context))).andReturn(response);
|
||||
EasyMock.replay(client);
|
||||
EasyMock.expect(backend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.same(context),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(Proxies.enhanceResponse(response));
|
||||
EasyMock.replay(backend);
|
||||
|
||||
CachingHttpClient t = new CachingHttpClient(client, resourceFactory, httpCacheStorage, cacheConfig);
|
||||
BasicHttpCache cache = new BasicHttpCache(resourceFactory, httpCacheStorage, cacheConfig);
|
||||
CachingExec t = new CachingExec(backend, cache, cacheConfig);
|
||||
|
||||
HttpResponse response1 = t.execute(get, context);
|
||||
HttpResponse response1 = t.execute(route, get, context, null);
|
||||
Assert.assertEquals(200, response1.getStatusLine().getStatusCode());
|
||||
EntityUtils.consume(response1.getEntity());
|
||||
|
||||
EasyMock.verify(client);
|
||||
EasyMock.verify(backend);
|
||||
|
||||
removeCache();
|
||||
|
||||
EasyMock.reset(client);
|
||||
EasyMock.expect(client.execute(
|
||||
EasyMock.eq(target),
|
||||
EasyMock.isA(HttpRequest.class),
|
||||
EasyMock.same(context))).andReturn(response);
|
||||
EasyMock.replay(client);
|
||||
EasyMock.reset(backend);
|
||||
EasyMock.expect(backend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.same(context),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(Proxies.enhanceResponse(response));
|
||||
EasyMock.replay(backend);
|
||||
|
||||
HttpResponse response2 = t.execute(get, context);
|
||||
HttpResponse response2 = t.execute(route, get, context, null);
|
||||
Assert.assertEquals(200, response2.getStatusLine().getStatusCode());
|
||||
EntityUtils.consume(response2.getEntity());
|
||||
|
||||
EasyMock.verify(client);
|
||||
EasyMock.verify(backend);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ import java.util.Date;
|
|||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.junit.Test;
|
||||
|
@ -49,7 +49,8 @@ public class TestProtocolAllowedBehavior extends AbstractProtocolTest {
|
|||
@Test
|
||||
public void testNonSharedCacheReturnsStaleResponseWhenRevalidationFailsForProxyRevalidate()
|
||||
throws Exception {
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1));
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
originResponse.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
@ -58,14 +59,15 @@ public class TestProtocolAllowedBehavior extends AbstractProtocolTest {
|
|||
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1));
|
||||
|
||||
backendExpectsAnyRequest().andThrow(new SocketTimeoutException());
|
||||
|
||||
replayMocks();
|
||||
behaveAsNonSharedCache();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
@ -74,17 +76,19 @@ public class TestProtocolAllowedBehavior extends AbstractProtocolTest {
|
|||
@Test
|
||||
public void testNonSharedCacheMayCacheResponsesWithCacheControlPrivate()
|
||||
throws Exception {
|
||||
HttpRequest req1 = new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1));
|
||||
originResponse.setHeader("Cache-Control","private,max-age=3600");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET","/", HttpVersion.HTTP_1_1));
|
||||
|
||||
replayMocks();
|
||||
behaveAsNonSharedCache();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
|
|
@ -37,13 +37,17 @@ 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.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.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.client.execchain.ClientExecChain;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.easymock.Capture;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
|
@ -74,24 +78,27 @@ public class TestProtocolDeviations {
|
|||
private int entityLength = 128;
|
||||
|
||||
private HttpHost host;
|
||||
private HttpRoute route;
|
||||
private HttpEntity body;
|
||||
private HttpEntity mockEntity;
|
||||
private HttpClient mockBackend;
|
||||
private ClientExecChain mockBackend;
|
||||
private HttpCache mockCache;
|
||||
private HttpRequest request;
|
||||
private HttpResponse originResponse;
|
||||
private CloseableHttpResponse originResponse;
|
||||
|
||||
private CachingHttpClient impl;
|
||||
private CachingExec impl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
|
||||
route = new HttpRoute(host);
|
||||
|
||||
body = makeBody(entityLength);
|
||||
|
||||
request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
|
||||
|
||||
originResponse = make200Response();
|
||||
originResponse = Proxies.enhanceResponse(make200Response());
|
||||
|
||||
CacheConfig config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
|
@ -99,11 +106,11 @@ public class TestProtocolDeviations {
|
|||
.build();
|
||||
|
||||
HttpCache cache = new BasicHttpCache(config);
|
||||
mockBackend = EasyMock.createNiceMock(HttpClient.class);
|
||||
mockBackend = EasyMock.createNiceMock(ClientExecChain.class);
|
||||
mockEntity = EasyMock.createNiceMock(HttpEntity.class);
|
||||
mockCache = EasyMock.createNiceMock(HttpCache.class);
|
||||
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
}
|
||||
|
||||
private HttpResponse make200Response() {
|
||||
|
@ -162,7 +169,7 @@ public class TestProtocolDeviations {
|
|||
|
||||
replayMocks();
|
||||
|
||||
HttpResponse response = impl.execute(host, post);
|
||||
HttpResponse response = impl.execute(route, HttpRequestWrapper.wrap(post));
|
||||
|
||||
verifyMocks();
|
||||
|
||||
|
@ -208,16 +215,19 @@ public class TestProtocolDeviations {
|
|||
org.easymock.EasyMock.expect(mockBody.getContentLength()).andReturn(-1L).anyTimes();
|
||||
post.setEntity(mockBody);
|
||||
|
||||
Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
|
||||
.capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
|
||||
originResponse).times(0, 1);
|
||||
Capture<HttpRequestWrapper> reqCap = new Capture<HttpRequestWrapper>();
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.capture(reqCap),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(
|
||||
originResponse).times(0, 1);
|
||||
|
||||
replayMocks();
|
||||
EasyMock.replay(mockBody);
|
||||
|
||||
HttpResponse result = impl.execute(host, post);
|
||||
HttpResponse result = impl.execute(route, HttpRequestWrapper.wrap(post));
|
||||
|
||||
verifyMocks();
|
||||
EasyMock.verify(mockBody);
|
||||
|
@ -247,14 +257,16 @@ public class TestProtocolDeviations {
|
|||
options.setEntity(body);
|
||||
options.setHeader("Content-Length", "1");
|
||||
|
||||
Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
|
||||
.capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
|
||||
originResponse);
|
||||
Capture<HttpRequestWrapper> reqCap = new Capture<HttpRequestWrapper>();
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.capture(reqCap),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
|
||||
impl.execute(host, options);
|
||||
impl.execute(route, HttpRequestWrapper.wrap(options));
|
||||
|
||||
verifyMocks();
|
||||
|
||||
|
@ -281,19 +293,22 @@ public class TestProtocolDeviations {
|
|||
// this situation, but it better not just pass the response
|
||||
// on.
|
||||
request.removeHeaders("Range");
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
|
||||
"Partial Content");
|
||||
originResponse = Proxies.enhanceResponse(
|
||||
new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
|
||||
"Partial Content"));
|
||||
originResponse.setHeader("Content-Range", "bytes 0-499/1234");
|
||||
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);
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
try {
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
HttpResponse result = impl.execute(route, HttpRequestWrapper.wrap(request));
|
||||
Assert.assertTrue(HttpStatus.SC_PARTIAL_CONTENT != result.getStatusLine()
|
||||
.getStatusCode());
|
||||
} catch (ClientProtocolException acceptableBehavior) {
|
||||
|
@ -311,14 +326,17 @@ public class TestProtocolDeviations {
|
|||
@Test
|
||||
public void testPassesOnOrigin401ResponseWithoutWWWAuthenticateHeader() throws Exception {
|
||||
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 401, "Unauthorized");
|
||||
originResponse = Proxies.enhanceResponse(
|
||||
new BasicHttpResponse(HTTP_1_1, 401, "Unauthorized"));
|
||||
|
||||
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);
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
HttpResponse result = impl.execute(route, HttpRequestWrapper.wrap(request));
|
||||
verifyMocks();
|
||||
Assert.assertSame(originResponse, result);
|
||||
}
|
||||
|
@ -331,14 +349,17 @@ public class TestProtocolDeviations {
|
|||
*/
|
||||
@Test
|
||||
public void testPassesOnOrigin405WithoutAllowHeader() throws Exception {
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 405, "Method Not Allowed");
|
||||
originResponse = Proxies.enhanceResponse(
|
||||
new BasicHttpResponse(HTTP_1_1, 405, "Method Not Allowed"));
|
||||
|
||||
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);
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
HttpResponse result = impl.execute(route, HttpRequestWrapper.wrap(request));
|
||||
verifyMocks();
|
||||
Assert.assertSame(originResponse, result);
|
||||
}
|
||||
|
@ -352,14 +373,17 @@ public class TestProtocolDeviations {
|
|||
*/
|
||||
@Test
|
||||
public void testPassesOnOrigin407WithoutAProxyAuthenticateHeader() throws Exception {
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 407, "Proxy Authentication Required");
|
||||
originResponse = Proxies.enhanceResponse(
|
||||
new BasicHttpResponse(HTTP_1_1, 407, "Proxy Authentication Required"));
|
||||
|
||||
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);
|
||||
EasyMock.expect(
|
||||
mockBackend.execute(
|
||||
EasyMock.eq(route),
|
||||
EasyMock.isA(HttpRequestWrapper.class),
|
||||
EasyMock.isA(HttpClientContext.class),
|
||||
EasyMock.<HttpExecutionAware>isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
HttpResponse result = impl.execute(route, HttpRequestWrapper.wrap(request));
|
||||
verifyMocks();
|
||||
Assert.assertSame(originResponse, result);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -33,10 +33,10 @@ import java.util.Date;
|
|||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
|
@ -68,20 +68,20 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
public void testStaleIfErrorInResponseIsTrueReturnsStaleEntryWithWarning()
|
||||
throws Exception{
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
|
@ -91,13 +91,13 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
public void testConsumesErrorResponseWhenServingStale()
|
||||
throws Exception{
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
byte[] body = HttpTestUtils.getRandomBytes(101);
|
||||
ByteArrayInputStream buf = new ByteArrayInputStream(body);
|
||||
|
@ -105,11 +105,11 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
HttpEntity entity = new InputStreamEntity(cis, 101);
|
||||
resp2.setEntity(entity);
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertTrue(cis.wasClosed());
|
||||
|
@ -119,20 +119,20 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
public void testStaleIfErrorInResponseYieldsToMustRevalidate()
|
||||
throws Exception{
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60, must-revalidate");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
|
||||
|
@ -141,22 +141,22 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
@Test
|
||||
public void testStaleIfErrorInResponseYieldsToProxyRevalidateForSharedCache()
|
||||
throws Exception{
|
||||
assertTrue(impl.isSharedCache());
|
||||
assertTrue(config.isSharedCache());
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60, proxy-revalidate");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
|
||||
|
@ -167,23 +167,23 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
throws Exception{
|
||||
CacheConfig config = CacheConfig.custom()
|
||||
.setSharedCache(false).build();
|
||||
impl = new CachingHttpClient(mockBackend, config);
|
||||
impl = new CachingExec(mockBackend, new BasicHttpCache(config), config);
|
||||
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60, proxy-revalidate");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
|
@ -193,21 +193,21 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
public void testStaleIfErrorInResponseYieldsToExplicitFreshnessRequest()
|
||||
throws Exception{
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
req2.setHeader("Cache-Control","min-fresh=2");
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
|
||||
|
@ -217,21 +217,21 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
|
||||
throws Exception{
|
||||
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
req2.setHeader("Cache-Control","public, stale-if-error=60");
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
|
@ -242,20 +242,20 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
throws Exception{
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=2");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||
|
@ -267,21 +267,21 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
throws Exception{
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5");
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest());
|
||||
req2.setHeader("Cache-Control","stale-if-error=2");
|
||||
HttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host,req1);
|
||||
HttpResponse result = impl.execute(host,req2);
|
||||
impl.execute(route,req1);
|
||||
HttpResponse result = impl.execute(route,req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||
|
@ -305,9 +305,10 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
.setAsynchronousWorkersMax(1)
|
||||
.build();
|
||||
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
@ -315,13 +316,14 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1).times(1,2);
|
||||
backendExpectsAnyRequestAndReturn(resp1).times(1,2);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
@ -347,9 +349,10 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
.setAsynchronousWorkersMax(1)
|
||||
.setSharedCache(false)
|
||||
.build();
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
@ -357,14 +360,15 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1).times(1,2);
|
||||
backendExpectsAnyRequestAndReturn(resp1).times(1,2);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
req2.setHeader("If-None-Match","\"etag\"");
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
|
||||
|
@ -393,27 +397,29 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkersMax(1)
|
||||
.build();
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatDate(now));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
@ -442,27 +448,29 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
.setAsynchronousWorkersMax(1)
|
||||
.setSharedCache(true)
|
||||
.build();
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatDate(now));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
@ -491,28 +499,30 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
|
|||
.setAsynchronousWorkersMax(1)
|
||||
.setSharedCache(true)
|
||||
.build();
|
||||
impl = new CachingHttpClient(mockBackend, cache, config);
|
||||
impl = new CachingExec(mockBackend, cache, config);
|
||||
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequestAndReturn(resp1);
|
||||
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
|
||||
new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1));
|
||||
req2.setHeader("Cache-Control","min-fresh=2");
|
||||
HttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatDate(now));
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
backendExpectsAnyRequestAndReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
impl.execute(route, req1);
|
||||
HttpResponse result = impl.execute(route, req2);
|
||||
verifyMocks();
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getStatusLine().getStatusCode());
|
||||
|
|
|
@ -31,12 +31,12 @@ import net.sf.ehcache.config.CacheConfiguration;
|
|||
import net.sf.ehcache.config.Configuration;
|
||||
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.cache.HttpCacheStorage;
|
||||
import org.apache.http.impl.client.cache.CacheConfig;
|
||||
import org.apache.http.impl.client.cache.CachingHttpClient;
|
||||
import org.apache.http.impl.client.cache.CachingExec;
|
||||
import org.apache.http.impl.client.cache.HeapResourceFactory;
|
||||
import org.apache.http.impl.client.cache.TestProtocolRequirements;
|
||||
import org.apache.http.impl.client.execchain.ClientExecChain;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
|
@ -70,9 +70,9 @@ public class TestEhcacheProtocolRequirements extends TestProtocolRequirements{
|
|||
}
|
||||
CACHE_MANAGER.addCache(TEST_EHCACHE_NAME);
|
||||
HttpCacheStorage storage = new EhcacheHttpCacheStorage(CACHE_MANAGER.getCache(TEST_EHCACHE_NAME));
|
||||
mockBackend = EasyMock.createNiceMock(HttpClient.class);
|
||||
mockBackend = EasyMock.createNiceMock(ClientExecChain.class);
|
||||
|
||||
impl = new CachingHttpClient(mockBackend, new HeapResourceFactory(), storage, config);
|
||||
impl = new CachingExec(mockBackend, new HeapResourceFactory(), storage, config);
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
|
@ -109,7 +109,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.putEntry(url, value);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCachePutFailsSilentlyWhenWeCannotHashAKey() throws IOException {
|
||||
final String url = "foo";
|
||||
|
@ -127,7 +127,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.putEntry(url, value);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
public void testThrowsIOExceptionWhenMemcachedPutTimesOut() {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
|
@ -170,11 +170,11 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.putEntry(url, value);
|
||||
fail("should have thrown exception");
|
||||
} catch (IOException expected) {
|
||||
|
||||
|
||||
}
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSuccessfulCacheGet() throws UnsupportedEncodingException,
|
||||
IOException {
|
||||
|
@ -182,7 +182,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
final String key = "key";
|
||||
byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
|
||||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.get(key)).andReturn(serialized);
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
|
@ -190,57 +190,57 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
mockMemcachedCacheEntry.set(serialized);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(cacheEntry);
|
||||
|
||||
|
||||
replayMocks();
|
||||
HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
verifyMocks();
|
||||
assertSame(cacheEntry, resultingEntry);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTreatsNoneByteArrayFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
|
||||
IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
|
||||
|
||||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.get(key)).andReturn(new Object());
|
||||
|
||||
|
||||
replayMocks();
|
||||
HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
verifyMocks();
|
||||
assertNull(resultingEntry);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTreatsNullFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
|
||||
IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
|
||||
|
||||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.get(key)).andReturn(null);
|
||||
|
||||
|
||||
replayMocks();
|
||||
HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
verifyMocks();
|
||||
assertNull(resultingEntry);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTreatsAsCacheMissIfCannotReconstituteEntry() throws UnsupportedEncodingException,
|
||||
IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
|
||||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.get(key)).andReturn(serialized);
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.andReturn(mockMemcachedCacheEntry);
|
||||
mockMemcachedCacheEntry.set(serialized);
|
||||
EasyMock.expectLastCall().andThrow(new MemcachedSerializationException(new Exception()));
|
||||
|
||||
|
||||
replayMocks();
|
||||
assertNull(impl.getEntry(url));
|
||||
verifyMocks();
|
||||
|
@ -250,9 +250,9 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
public void testTreatsAsCacheMissIfCantHashStorageKey() throws UnsupportedEncodingException,
|
||||
IOException {
|
||||
final String url = "foo";
|
||||
|
||||
|
||||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andThrow(new MemcachedKeyHashingException(new Exception()));
|
||||
|
||||
|
||||
replayMocks();
|
||||
assertNull(impl.getEntry(url));
|
||||
verifyMocks();
|
||||
|
@ -265,7 +265,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.get(key))
|
||||
.andThrow(new OperationTimeoutException(""));
|
||||
|
||||
|
||||
replayMocks();
|
||||
try {
|
||||
impl.getEntry(url);
|
||||
|
@ -285,7 +285,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.removeEntry(url);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheRemoveHandlesKeyHashingFailure() throws IOException {
|
||||
final String url = "foo";
|
||||
|
@ -302,7 +302,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
|
||||
EasyMock.expect(mockMemcachedClient.delete(key))
|
||||
.andThrow(new OperationTimeoutException(""));
|
||||
|
||||
|
||||
replayMocks();
|
||||
try {
|
||||
impl.removeEntry(url);
|
||||
|
@ -341,7 +341,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.updateEntry(url, callback);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateOverwritesNonMatchingHashCollision() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
|
@ -366,7 +366,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
.andReturn(mockMemcachedCacheEntry);
|
||||
mockMemcachedCacheEntry.set(oldBytes);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn("not" + url).anyTimes();
|
||||
|
||||
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.andReturn(mockMemcachedCacheEntry2);
|
||||
EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
|
||||
|
@ -389,7 +389,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
|
||||
CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
|
||||
final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
|
||||
|
||||
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
public HttpCacheEntry update(HttpCacheEntry old) {
|
||||
|
@ -406,7 +406,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
mockMemcachedCacheEntry.set(oldBytes);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
|
||||
|
||||
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.andReturn(mockMemcachedCacheEntry2);
|
||||
EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
|
||||
|
@ -419,7 +419,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
impl.updateEntry(url, callback);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateThrowsExceptionsIfCASFailsEnoughTimes() throws IOException {
|
||||
final String url = "foo";
|
||||
|
@ -429,7 +429,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
|
||||
CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
|
||||
final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
|
||||
CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(0).build();
|
||||
impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
|
||||
mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
|
||||
|
@ -449,7 +449,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
mockMemcachedCacheEntry.set(oldBytes);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
|
||||
|
||||
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.andReturn(mockMemcachedCacheEntry2);
|
||||
EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
|
||||
|
@ -467,7 +467,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateCanUpdateExistingEntryWithRetry() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
|
@ -499,7 +499,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
mockMemcachedCacheEntry.set(oldBytes);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
|
||||
EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
|
||||
|
||||
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.andReturn(mockMemcachedCacheEntry2);
|
||||
EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
|
||||
|
@ -515,7 +515,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
mockMemcachedCacheEntry3.set(oldBytes2);
|
||||
EasyMock.expect(mockMemcachedCacheEntry3.getStorageKey()).andReturn(url);
|
||||
EasyMock.expect(mockMemcachedCacheEntry3.getHttpCacheEntry()).andReturn(existingValue2);
|
||||
|
||||
|
||||
EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue2))
|
||||
.andReturn(mockMemcachedCacheEntry4);
|
||||
EasyMock.expect(mockMemcachedCacheEntry4.toByteArray()).andReturn(newBytes2);
|
||||
|
@ -523,7 +523,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
EasyMock.expect(
|
||||
mockMemcachedClient.cas(EasyMock.eq(key), EasyMock.eq(casValue2.getCas()),
|
||||
EasyMock.aryEq(newBytes2))).andReturn(CASResponse.OK);
|
||||
|
||||
|
||||
replayMocks();
|
||||
impl.updateEntry(url, callback);
|
||||
verifyMocks();
|
||||
|
@ -557,7 +557,7 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
|
|||
verifyMocks();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test(expected=HttpCacheUpdateException.class)
|
||||
public void testThrowsExceptionOnUpdateIfCannotHashStorageKey() throws Exception {
|
||||
final String url = "foo";
|
||||
|
|
|
@ -167,7 +167,7 @@ public class MainClientExec implements ClientExecChain {
|
|||
}
|
||||
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
ExecProxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
Proxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
Object userToken = context.getUserToken();
|
||||
|
@ -220,7 +220,7 @@ public class MainClientExec implements ClientExecChain {
|
|||
HttpResponse response = null;
|
||||
for (int execCount = 1;; execCount++) {
|
||||
|
||||
if (execCount > 1 && !ExecProxies.isRepeatable(request)) {
|
||||
if (execCount > 1 && !Proxies.isRepeatable(request)) {
|
||||
throw new NonRepeatableRequestException("Cannot retry request " +
|
||||
"with a non-repeatable request entity.");
|
||||
}
|
||||
|
@ -333,9 +333,9 @@ public class MainClientExec implements ClientExecChain {
|
|||
if (entity == null || !entity.isStreaming()) {
|
||||
// connection not needed and (assumed to be) in re-usable state
|
||||
releaseTrigger.releaseConnection();
|
||||
return ExecProxies.enhanceResponse(response, null);
|
||||
return Proxies.enhanceResponse(response, null);
|
||||
} else {
|
||||
return ExecProxies.enhanceResponse(response, releaseTrigger);
|
||||
return Proxies.enhanceResponse(response, releaseTrigger);
|
||||
}
|
||||
} catch (ConnectionShutdownException ex) {
|
||||
InterruptedIOException ioex = new InterruptedIOException(
|
||||
|
|
|
@ -16,7 +16,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
|
|||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class ExecProxies {
|
||||
class Proxies {
|
||||
|
||||
static void enhanceEntity(final HttpEntityEnclosingRequest request) {
|
||||
HttpEntity entity = request.getEntity();
|
|
@ -119,7 +119,7 @@ public class RedirectExec implements ClientExecChain {
|
|||
currentRequest = HttpRequestWrapper.wrap(redirect);
|
||||
currentRequest.setHeaders(original.getAllHeaders());
|
||||
if (original instanceof HttpEntityEnclosingRequest) {
|
||||
ExecProxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
Proxies.enhanceEntity((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
URI uri = currentRequest.getURI();
|
||||
|
|
|
@ -98,7 +98,7 @@ public class RetryExec implements ClientExecChain {
|
|||
if (this.log.isDebugEnabled()) {
|
||||
this.log.debug(ex.getMessage(), ex);
|
||||
}
|
||||
if (!ExecProxies.isRepeatable(request)) {
|
||||
if (!Proxies.isRepeatable(request)) {
|
||||
this.log.debug("Cannot retry non-repeatable request");
|
||||
throw new NonRepeatableRequestException("Cannot retry request " +
|
||||
"with a non-repeatable request entity", ex);
|
||||
|
|
Loading…
Reference in New Issue