HTTPCLIENT-979: cache entry resource management extracted from CachingHttpClient

Contributed by Jonathan Moore <jonathan_moore at comcast.com>


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@986864 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2010-08-18 19:15:43 +00:00
parent 5c33d5cfd9
commit 83c2c00109
24 changed files with 929 additions and 1167 deletions

View File

@ -27,19 +27,34 @@
package org.apache.http.client.cache;
import java.io.IOException;
import java.util.Date;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
/**
* @since 4.1
*/
public interface HttpCache {
void putEntry(String key, HttpCacheEntry entry) throws IOException;
void flushCacheEntriesFor(HttpHost host, HttpRequest request)
throws IOException;
HttpCacheEntry getEntry(String key) throws IOException;
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request)
throws IOException;
void removeEntry(String key) throws IOException;
HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request)
throws IOException;
void updateEntry(
String key, HttpCacheUpdateCallback callback) throws IOException;
HttpResponse cacheAndReturnResponse(
HttpHost host, HttpRequest request, HttpResponse originResponse,
Date requestSent, Date responseReceived)
throws IOException;
HttpResponse updateCacheEntry(
HttpHost target, HttpRequest request, HttpCacheEntry stale, HttpResponse originResponse,
Date requestSent, Date responseReceived)
throws IOException;
}

View File

@ -0,0 +1,45 @@
/*
* ====================================================================
* 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.client.cache;
import java.io.IOException;
/**
* @since 4.1
*/
public interface HttpCacheStorage {
void putEntry(String key, HttpCacheEntry entry) throws IOException;
HttpCacheEntry getEntry(String key) throws IOException;
void removeEntry(String key) throws IOException;
void updateEntry(
String key, HttpCacheUpdateCallback callback) throws IOException;
}

View File

@ -1,93 +1,204 @@
/*
* ====================================================================
* 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.LinkedHashMap;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.Resource;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
/**
* Basic {@link HttpCache} implementation backed by an instance of {@link LinkedHashMap}.
* This cache does NOT deallocate resources associated with the cache entries. It is intended
* for use with {@link MemCacheEntry} and similar.
*
* @since 4.1
*/
@ThreadSafe
public class BasicHttpCache implements HttpCache {
private final CacheMap entries;
private final URIExtractor uriExtractor;
private final ResourceFactory resourceFactory;
private final int maxObjectSizeBytes;
private final CacheEntryUpdater cacheEntryUpdater;
private final CachedHttpResponseGenerator responseGenerator;
private final CacheInvalidator cacheInvalidator;
private final HttpCacheStorage storage;
public BasicHttpCache(int maxEntries) {
this.entries = new CacheMap(maxEntries);
public BasicHttpCache(ResourceFactory resourceFactory, HttpCacheStorage storage, CacheConfig config) {
this.resourceFactory = resourceFactory;
this.uriExtractor = new URIExtractor();
this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory);
this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
this.responseGenerator = new CachedHttpResponseGenerator();
this.storage = storage;
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.storage);
}
/**
* Places a HttpCacheEntry in the cache
*
* @param url
* Url to use as the cache key
* @param entry
* HttpCacheEntry to place in the cache
*/
public synchronized void putEntry(String url, HttpCacheEntry entry) throws IOException {
entries.put(url, entry);
public BasicHttpCache(CacheConfig config) {
this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config);
}
/**
* Gets an entry from the cache, if it exists
*
* @param url
* Url that is the cache key
* @return HttpCacheEntry if one exists, or null for cache miss
*/
public synchronized HttpCacheEntry getEntry(String url) throws IOException {
return entries.get(url);
public BasicHttpCache() {
this(new CacheConfig());
}
/**
* Removes a HttpCacheEntry from the cache
*
* @param url
* Url that is the cache key
*/
public synchronized void removeEntry(String url) throws IOException {
entries.remove(url);
public void flushCacheEntriesFor(HttpHost host, HttpRequest request)
throws IOException {
String uri = uriExtractor.getURI(host, request);
storage.removeEntry(uri);
}
public synchronized void updateEntry(
String url,
HttpCacheUpdateCallback callback) throws IOException {
HttpCacheEntry existingEntry = entries.get(url);
entries.put(url, callback.update(existingEntry));
void storeInCache(
HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
if (entry.hasVariants()) {
storeVariantEntry(target, request, entry);
} else {
storeNonVariantEntry(target, request, entry);
}
}
}
void storeNonVariantEntry(
HttpHost target, HttpRequest req, HttpCacheEntry entry) throws IOException {
String uri = uriExtractor.getURI(target, req);
storage.putEntry(uri, entry);
}
void storeVariantEntry(
final HttpHost target,
final HttpRequest req,
final HttpCacheEntry entry) throws IOException {
final String parentURI = uriExtractor.getURI(target, req);
final String variantURI = uriExtractor.getVariantURI(target, req, entry);
storage.putEntry(variantURI, entry);
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry existing) throws IOException {
return doGetUpdatedParentEntry(
req.getRequestLine().getUri(), existing, entry, variantURI);
}
};
storage.updateEntry(parentURI, callback);
}
boolean isIncompleteResponse(HttpResponse resp, Resource resource) {
int status = resp.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK
&& status != HttpStatus.SC_PARTIAL_CONTENT) {
return false;
}
Header hdr = resp.getFirstHeader("Content-Length");
if (hdr == null) return false;
int contentLength;
try {
contentLength = Integer.parseInt(hdr.getValue());
} catch (NumberFormatException nfe) {
return false;
}
return (resource.length() < contentLength);
}
HttpResponse generateIncompleteResponseError(HttpResponse response,
Resource resource) {
int contentLength = Integer.parseInt(response.getFirstHeader("Content-Length").getValue());
HttpResponse error =
new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
error.setHeader("Content-Type","text/plain;charset=UTF-8");
String msg = String.format("Received incomplete response " +
"with Content-Length %d but actual body length %d",
contentLength, resource.length());
byte[] msgBytes = msg.getBytes();
error.setHeader("Content-Length", Integer.toString(msgBytes.length));
error.setEntity(new ByteArrayEntity(msgBytes));
return error;
}
HttpCacheEntry doGetUpdatedParentEntry(
final String requestId,
final HttpCacheEntry existing,
final HttpCacheEntry entry,
final String variantURI) throws IOException {
HttpCacheEntry src = existing;
if (src == null) {
src = entry;
}
Set<String> variants = new HashSet<String>(src.getVariantURIs());
variants.add(variantURI);
Resource resource = resourceFactory.copy(requestId, src.getResource());
return new HttpCacheEntry(
src.getRequestDate(),
src.getResponseDate(),
src.getStatusLine(),
src.getAllHeaders(),
resource,
variants);
}
public HttpResponse updateCacheEntry(HttpHost target, HttpRequest request,
HttpCacheEntry stale, HttpResponse originResponse,
Date requestSent, Date responseReceived) throws IOException {
HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(
request.getRequestLine().getUri(),
stale,
requestSent,
responseReceived,
originResponse);
storeInCache(target, request, updatedEntry);
return responseGenerator.generateResponse(updatedEntry);
}
public HttpResponse cacheAndReturnResponse(HttpHost host, HttpRequest request,
HttpResponse originResponse, Date requestSent, Date responseReceived)
throws IOException {
SizeLimitedResponseReader responseReader = getResponseReader(request, originResponse);
responseReader.readResponse();
if (responseReader.isLimitReached()) {
return responseReader.getReconstructedResponse();
}
Resource resource = responseReader.getResource();
if (isIncompleteResponse(originResponse, resource)) {
return generateIncompleteResponseError(originResponse, resource);
}
HttpCacheEntry entry = new HttpCacheEntry(
requestSent,
responseReceived,
originResponse.getStatusLine(),
originResponse.getAllHeaders(),
resource,
null);
storeInCache(host, request, entry);
return responseGenerator.generateResponse(entry);
}
SizeLimitedResponseReader getResponseReader(HttpRequest request, HttpResponse backEndResponse) {
return new SizeLimitedResponseReader(
resourceFactory, maxObjectSizeBytes, request, backEndResponse);
}
public HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request) throws IOException {
HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request));
if (root == null) return null;
if (!root.hasVariants()) return root;
HttpCacheEntry variant = storage.getEntry(uriExtractor.getVariantURI(host, request, root));
return variant;
}
public void flushInvalidatedCacheEntriesFor(HttpHost host,
HttpRequest request) throws IOException {
cacheInvalidator.flushInvalidatedCacheEntries(host, request);
}
}

View File

@ -0,0 +1,94 @@
/*
* ====================================================================
* 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.LinkedHashMap;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
/**
* Basic {@link HttpCacheStorage} implementation backed by an instance of {@link LinkedHashMap}.
* This cache does NOT deallocate resources associated with the cache entries. It is intended
* for use with {@link MemCacheEntry} and similar.
*
* @since 4.1
*/
@ThreadSafe
public class BasicHttpCacheStorage implements HttpCacheStorage {
private final CacheMap entries;
public BasicHttpCacheStorage(CacheConfig config) {
super();
this.entries = new CacheMap(config.getMaxCacheEntries());
}
/**
* Places a HttpCacheEntry in the cache
*
* @param url
* Url to use as the cache key
* @param entry
* HttpCacheEntry to place in the cache
*/
public synchronized void putEntry(String url, HttpCacheEntry entry) throws IOException {
entries.put(url, entry);
}
/**
* Gets an entry from the cache, if it exists
*
* @param url
* Url that is the cache key
* @return HttpCacheEntry if one exists, or null for cache miss
*/
public synchronized HttpCacheEntry getEntry(String url) throws IOException {
return entries.get(url);
}
/**
* Removes a HttpCacheEntry from the cache
*
* @param url
* Url that is the cache key
*/
public synchronized void removeEntry(String url) throws IOException {
entries.remove(url);
}
public synchronized void updateEntry(
String url,
HttpCacheUpdateCallback callback) throws IOException {
HttpCacheEntry existingEntry = entries.get(url);
entries.put(url, callback.update(existingEntry));
}
}

View File

@ -37,7 +37,14 @@ public class CacheConfig {
*/
public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
/** Default setting for the maximum number of cache entries
* that will be retained.
*/
public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000;
private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
private boolean isSharedCache = true;
/**
@ -74,4 +81,19 @@ public class CacheConfig {
this.isSharedCache = isSharedCache;
}
/**
* Returns the maximum number of cache entries the cache will retain.
* @return int
*/
public int getMaxCacheEntries() {
return maxCacheEntries;
}
/**
* Sets the maximum number of cache entries the cache will retain.
* @param maxCacheEntries int
*/
public void setMaxCacheEntries(int maxCacheEntries) {
this.maxCacheEntries = maxCacheEntries;
}
}

View File

@ -40,6 +40,7 @@ import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
/**
* Given a particular HttpRequest, flush any cache entries that this request
@ -50,7 +51,7 @@ import org.apache.http.client.cache.HttpCacheEntry;
@ThreadSafe // so long as the cache implementation is thread-safe
class CacheInvalidator {
private final HttpCache cache;
private final HttpCacheStorage storage;
private final URIExtractor uriExtractor;
private final Log log = LogFactory.getLog(getClass());
@ -60,13 +61,13 @@ class CacheInvalidator {
* {@link URIExtractor}.
*
* @param uriExtractor Provides identifiers for the keys to store cache entries
* @param cache the cache to store items away in
* @param storage the cache to store items away in
*/
public CacheInvalidator(
final URIExtractor uriExtractor,
final HttpCache cache) {
final HttpCacheStorage storage) {
this.uriExtractor = uriExtractor;
this.cache = cache;
this.storage = storage;
}
/**
@ -82,15 +83,15 @@ class CacheInvalidator {
String theUri = uriExtractor.getURI(host, req);
HttpCacheEntry parent = cache.getEntry(theUri);
HttpCacheEntry parent = storage.getEntry(theUri);
log.debug("parent entry: " + parent);
if (parent != null) {
for (String variantURI : parent.getVariantURIs()) {
cache.removeEntry(variantURI);
storage.removeEntry(variantURI);
}
cache.removeEntry(theUri);
storage.removeEntry(theUri);
}
URL reqURL;
try {
@ -115,7 +116,7 @@ class CacheInvalidator {
protected void flushUriIfSameHost(URL requestURL, URL targetURL) throws IOException {
if (targetURL.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
cache.removeEntry(targetURL.toString());
storage.removeEntry(targetURL.toString());
}
}

View File

@ -29,14 +29,11 @@ package org.apache.http.impl.client.cache;
import java.io.IOException;
import java.net.URI;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@ -52,12 +49,8 @@ import org.apache.http.client.ResponseHandler;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.Resource;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
@ -70,7 +63,6 @@ import org.apache.http.protocol.HttpContext;
@ThreadSafe // So long as the responseCache implementation is threadsafe
public class CachingHttpClient implements HttpClient {
private final static int MAX_CACHE_ENTRIES = 1000;
private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
private final AtomicLong cacheHits = new AtomicLong();
@ -79,19 +71,14 @@ public class CachingHttpClient implements HttpClient {
private final HttpClient backend;
private final HttpCache responseCache;
private final ResourceFactory resourceFactory;
private final CacheValidityPolicy validityPolicy;
private final ResponseCachingPolicy responseCachingPolicy;
private final URIExtractor uriExtractor;
private final CachedHttpResponseGenerator responseGenerator;
private final CacheInvalidator cacheInvalidator;
private final CacheableRequestPolicy cacheableRequestPolicy;
private final CachedResponseSuitabilityChecker suitabilityChecker;
private final ConditionalRequestBuilder conditionalRequestBuilder;
private final CacheEntryUpdater cacheEntryUpdater;
private final int maxObjectSizeBytes;
private final boolean sharedCache;
@ -103,7 +90,6 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient(
HttpClient client,
HttpCache cache,
ResourceFactory resourceFactory,
CacheConfig config) {
super();
if (client == null) {
@ -112,9 +98,6 @@ public class CachingHttpClient implements HttpClient {
if (cache == null) {
throw new IllegalArgumentException("HttpCache may not be null");
}
if (resourceFactory == null) {
throw new IllegalArgumentException("ResourceFactory may not be null");
}
if (config == null) {
throw new IllegalArgumentException("CacheConfig may not be null");
}
@ -122,16 +105,12 @@ public class CachingHttpClient implements HttpClient {
this.sharedCache = config.isSharedCache();
this.backend = client;
this.responseCache = cache;
this.resourceFactory = resourceFactory;
this.validityPolicy = new CacheValidityPolicy();
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache);
this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
this.uriExtractor = new URIExtractor();
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
this.cacheableRequestPolicy = new CacheableRequestPolicy();
this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy);
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
this.cacheEntryUpdater = new CacheEntryUpdater(this.resourceFactory);
this.responseCompliance = new ResponseProtocolCompliance();
this.requestCompliance = new RequestProtocolCompliance();
@ -139,91 +118,73 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient() {
this(new DefaultHttpClient(),
new BasicHttpCache(MAX_CACHE_ENTRIES),
new HeapResourceFactory(),
new BasicHttpCache(),
new CacheConfig());
}
public CachingHttpClient(CacheConfig config) {
this(new DefaultHttpClient(),
new BasicHttpCache(MAX_CACHE_ENTRIES),
new HeapResourceFactory(),
new BasicHttpCache(config),
config);
}
public CachingHttpClient(HttpClient client) {
this(client,
new BasicHttpCache(MAX_CACHE_ENTRIES),
new HeapResourceFactory(),
new BasicHttpCache(),
new CacheConfig());
}
public CachingHttpClient(HttpClient client, CacheConfig config) {
this(client,
new BasicHttpCache(MAX_CACHE_ENTRIES),
new HeapResourceFactory(),
new BasicHttpCache(config),
config);
}
public CachingHttpClient(
HttpCache cache,
ResourceFactory resourceFactory) {
HttpCache cache) {
this(new DefaultHttpClient(),
cache,
resourceFactory,
new CacheConfig());
}
public CachingHttpClient(
HttpCache cache,
ResourceFactory resourceFactory,
CacheConfig config) {
this(new DefaultHttpClient(),
cache,
resourceFactory,
config);
}
public CachingHttpClient(
HttpClient client,
HttpCache cache,
ResourceFactory resourceFactory) {
HttpCache cache) {
this(client,
cache,
resourceFactory,
new CacheConfig());
}
CachingHttpClient(
HttpClient backend,
ResourceFactory resourceFactory,
CacheValidityPolicy validityPolicy,
ResponseCachingPolicy responseCachingPolicy,
URIExtractor uriExtractor,
HttpCache responseCache,
CachedHttpResponseGenerator responseGenerator,
CacheInvalidator cacheInvalidator,
CacheableRequestPolicy cacheableRequestPolicy,
CachedResponseSuitabilityChecker suitabilityChecker,
ConditionalRequestBuilder conditionalRequestBuilder,
CacheEntryUpdater entryUpdater,
ResponseProtocolCompliance responseCompliance,
RequestProtocolCompliance requestCompliance) {
CacheConfig config = new CacheConfig();
this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
this.sharedCache = config.isSharedCache();
this.backend = backend;
this.resourceFactory = resourceFactory;
this.validityPolicy = validityPolicy;
this.responseCachingPolicy = responseCachingPolicy;
this.uriExtractor = uriExtractor;
this.responseCache = responseCache;
this.responseGenerator = responseGenerator;
this.cacheInvalidator = cacheInvalidator;
this.cacheableRequestPolicy = cacheableRequestPolicy;
this.suitabilityChecker = suitabilityChecker;
this.conditionalRequestBuilder = conditionalRequestBuilder;
this.cacheEntryUpdater = entryUpdater;
this.responseCompliance = responseCompliance;
this.requestCompliance = requestCompliance;
}
@ -409,13 +370,13 @@ public class CachingHttpClient implements HttpClient {
throw new ClientProtocolException(e);
}
cacheInvalidator.flushInvalidatedCacheEntries(target, request);
responseCache.flushInvalidatedCacheEntriesFor(target, request);
if (!cacheableRequestPolicy.isServableFromCache(request)) {
return callBackend(target, request, context);
}
HttpCacheEntry entry = getCacheEntry(target, request);
HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
if (entry == null) {
cacheMisses.getAndIncrement();
if (log.isDebugEnabled()) {
@ -471,18 +432,6 @@ public class CachingHttpClient implements HttpClient {
return new Date();
}
HttpCacheEntry getCacheEntry(HttpHost target, HttpRequest request) throws IOException {
String uri = uriExtractor.getURI(target, request);
HttpCacheEntry entry = responseCache.getEntry(uri);
if (entry == null || !entry.hasVariants()) {
return entry;
}
String variantUri = uriExtractor.getVariantURI(target, request, entry);
return responseCache.getEntry(variantUri);
}
boolean clientRequestsOurOptions(HttpRequest request) {
RequestLine line = request.getRequestLine();
@ -533,101 +482,14 @@ public class CachingHttpClient implements HttpClient {
int statusCode = backendResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
cacheUpdates.getAndIncrement();
HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(
request.getRequestLine().getUri(),
cacheEntry,
requestDate,
responseDate,
backendResponse);
storeInCache(target, request, updatedEntry);
return responseGenerator.generateResponse(updatedEntry);
return responseCache.updateCacheEntry(target, request, cacheEntry,
backendResponse, requestDate, responseDate);
}
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
backendResponse);
}
void storeInCache(
HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
if (entry.hasVariants()) {
storeVariantEntry(target, request, entry);
} else {
storeNonVariantEntry(target, request, entry);
}
}
void storeNonVariantEntry(
HttpHost target, HttpRequest req, HttpCacheEntry entry) throws IOException {
String uri = uriExtractor.getURI(target, req);
responseCache.putEntry(uri, entry);
}
void storeVariantEntry(
final HttpHost target,
final HttpRequest req,
final HttpCacheEntry entry) throws IOException {
final String parentURI = uriExtractor.getURI(target, req);
final String variantURI = uriExtractor.getVariantURI(target, req, entry);
responseCache.putEntry(variantURI, entry);
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry existing) throws IOException {
return doGetUpdatedParentEntry(
req.getRequestLine().getUri(), existing, entry, variantURI);
}
};
responseCache.updateEntry(parentURI, callback);
}
HttpCacheEntry doGetUpdatedParentEntry(
final String requestId,
final HttpCacheEntry existing,
final HttpCacheEntry entry,
final String variantURI) throws IOException {
HttpCacheEntry src = existing;
if (src == null) {
src = entry;
}
Set<String> variants = new HashSet<String>(src.getVariantURIs());
variants.add(variantURI);
Resource resource = resourceFactory.copy(requestId, src.getResource());
return new HttpCacheEntry(
src.getRequestDate(),
src.getResponseDate(),
src.getStatusLine(),
src.getAllHeaders(),
resource,
variants);
}
HttpResponse correctIncompleteResponse(HttpResponse resp, Resource resource) {
int status = resp.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK
&& status != HttpStatus.SC_PARTIAL_CONTENT) {
return resp;
}
Header hdr = resp.getFirstHeader("Content-Length");
if (hdr == null) return resp;
int contentLength;
try {
contentLength = Integer.parseInt(hdr.getValue());
} catch (NumberFormatException nfe) {
return resp;
}
if (resource.length() >= contentLength) return resp;
HttpResponse error =
new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
error.setHeader("Content-Type","text/plain;charset=UTF-8");
String msg = String.format("Received incomplete response " +
"with Content-Length %d but actual body length %d", contentLength, resource.length());
byte[] msgBytes = msg.getBytes();
error.setHeader("Content-Length", Integer.toString(msgBytes.length));
error.setEntity(new ByteArrayEntity(msgBytes));
return error;
}
HttpResponse handleBackendResponse(
HttpHost target,
HttpRequest request,
@ -640,40 +502,13 @@ public class CachingHttpClient implements HttpClient {
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
HttpResponse corrected = backendResponse;
if (cacheable) {
SizeLimitedResponseReader responseReader = getResponseReader(request, backendResponse);
responseReader.readResponse();
if (responseReader.isLimitReached()) {
return responseReader.getReconstructedResponse();
}
Resource resource = responseReader.getResource();
corrected = correctIncompleteResponse(backendResponse, resource);
int correctedStatus = corrected.getStatusLine().getStatusCode();
if (HttpStatus.SC_BAD_GATEWAY != correctedStatus) {
HttpCacheEntry entry = new HttpCacheEntry(
requestDate,
responseDate,
corrected.getStatusLine(),
corrected.getAllHeaders(),
resource,
null);
storeInCache(target, request, entry);
return responseGenerator.generateResponse(entry);
}
return responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate,
responseDate);
}
String uri = uriExtractor.getURI(target, request);
responseCache.removeEntry(uri);
return corrected;
}
SizeLimitedResponseReader getResponseReader(HttpRequest request, HttpResponse backEndResponse) {
return new SizeLimitedResponseReader(
resourceFactory, maxObjectSizeBytes, request, backEndResponse);
responseCache.flushCacheEntriesFor(target, request);
return backendResponse;
}
}

View File

@ -33,13 +33,13 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.Resource;
/**
* {@link HttpCache} implementation capable of deallocating resources associated with
* {@link HttpCacheStorage} implementation capable of deallocating resources associated with
* the cache entries. This cache keeps track of cache entries using {@link PhantomReference}
* and maintains a collection of all resources that are no longer in use. The cache, however,
* does not automatically deallocates associated resources by invoking {@link Resource#dispose()}
@ -47,13 +47,13 @@ import org.apache.http.client.cache.Resource;
* resource deallocation. The cache can be permanently shut down using {@link #shutdown()}
* method. All resources associated with the entries used by the cache will be deallocated.
*
* This {@link HttpCache} implementation is intended for use with {@link FileCacheEntry}
* This {@link HttpCacheStorage} implementation is intended for use with {@link FileCacheEntry}
* and similar.
*
* @since 4.1
*/
@ThreadSafe
public class ManagedHttpCache implements HttpCache {
public class ManagedHttpCacheStorage implements HttpCacheStorage {
private final CacheMap entries;
private final ReferenceQueue<HttpCacheEntry> morque;
@ -61,9 +61,9 @@ public class ManagedHttpCache implements HttpCache {
private volatile boolean shutdown;
public ManagedHttpCache(int maxEntries) {
public ManagedHttpCacheStorage(final CacheConfig config) {
super();
this.entries = new CacheMap(maxEntries);
this.entries = new CacheMap(config.getMaxCacheEntries());
this.morque = new ReferenceQueue<HttpCacheEntry>();
this.resources = new HashSet<ResourceReference>();
}

View File

@ -32,7 +32,6 @@ import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.client.cache.InputLimit;
import org.apache.http.client.cache.Resource;
@ -116,8 +115,7 @@ class SizeLimitedResponseReader {
HttpResponse getReconstructedResponse() throws IOException {
ensureConsumed();
HttpResponse reconstructed = new BasicHttpResponse(response.getProtocolVersion(),
HttpStatus.SC_OK, "Success");
HttpResponse reconstructed = new BasicHttpResponse(response.getStatusLine());
reconstructed.setHeaders(response.getAllHeaders());
reconstructed.setEntity(new CombinedEntity(resource, instream));
return reconstructed;

View File

@ -31,15 +31,15 @@ import java.io.IOException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
public class EhcacheHttpCache implements HttpCache {
public class EhcacheHttpCacheStorage implements HttpCacheStorage {
private final Ehcache cache;
public EhcacheHttpCache(Ehcache cache) {
public EhcacheHttpCacheStorage(Ehcache cache) {
this.cache = cache;
}

View File

@ -48,12 +48,13 @@ public abstract class AbstractProtocolTest {
originResponse = make200Response();
cache = new BasicHttpCache(MAX_ENTRIES);
params = new CacheConfig();
params.setMaxCacheEntries(MAX_ENTRIES);
params.setMaxObjectSizeBytes(MAX_BYTES);
cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, cache, params);
}
protected void replayMocks() {
@ -89,18 +90,23 @@ public abstract class AbstractProtocolTest {
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, mockCache, params);
EasyMock.expect(mockCache.getEntry((String) EasyMock.anyObject())).andReturn(null)
.anyTimes();
EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
.andReturn(null).anyTimes();
mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.removeEntry(EasyMock.isA(String.class));
mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
}
protected void behaveAsNonSharedCache() {
params.setSharedCache(false);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, cache, params);
}
public AbstractProtocolTest() {

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.client.cache;
import java.util.Date;
import java.util.Set;
import org.apache.http.Header;
import org.apache.http.client.cache.HttpCacheEntry;
@ -78,4 +79,8 @@ public class CacheEntry extends HttpCacheEntry {
super(new Date(), new Date(), new OKStatus(), new Header[] {}, new HeapResource(content), null);
}
public CacheEntry(Set<String> variants) {
super(new Date(), new Date(), new OKStatus(), new Header[] {}, BODY, variants);
}
}

View File

@ -75,14 +75,15 @@ public class DoNotTestProtocolRequirements {
request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
originResponse = make200Response();
CacheConfig params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
params.setMaxCacheEntries(MAX_ENTRIES);
HttpCache cache = new BasicHttpCache(MAX_ENTRIES);
HttpCache cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockEntity = EasyMock.createMock(HttpEntity.class);
mockCache = EasyMock.createMock(HttpCache.class);
CacheConfig params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, cache, params);
}
private HttpResponse make200Response() {

View File

@ -195,9 +195,13 @@ public class HttpTestUtils {
*/
public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2)
throws Exception {
return (equivalent(r1.getEntity(), r2.getEntity())
&& semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine()) && isEndToEndHeaderSubset(
r1, r2));
final boolean entitiesEquivalent = equivalent(r1.getEntity(), r2.getEntity());
if (!entitiesEquivalent) return false;
final boolean statusLinesEquivalent = semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine());
if (!statusLinesEquivalent) return false;
final boolean e2eHeadersEquivalentSubset = isEndToEndHeaderSubset(
r1, r2);
return e2eHeadersEquivalentSubset;
}
/* Assert.asserts that two requests are morally equivalent. */

View File

@ -0,0 +1,64 @@
/*
* ====================================================================
* 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.HashMap;
import java.util.Map;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
class SimpleHttpCacheStorage implements HttpCacheStorage {
public final Map<String,HttpCacheEntry> map;
public SimpleHttpCacheStorage() {
map = new HashMap<String,HttpCacheEntry>();
}
public void putEntry(String key, HttpCacheEntry entry) throws IOException {
map.put(key, entry);
}
public HttpCacheEntry getEntry(String key) throws IOException {
return map.get(key);
}
public void removeEntry(String key) throws IOException {
map.remove(key);
}
public void updateEntry(String key, HttpCacheUpdateCallback callback)
throws IOException {
HttpCacheEntry v1 = map.get(key);
HttpCacheEntry v2 = callback.update(v1);
map.put(key,v2);
}
}

View File

@ -0,0 +1,335 @@
/*
* ====================================================================
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.InputStream;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.Resource;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHttpResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TestBasicHttpCache {
private BasicHttpCache impl;
private SimpleHttpCacheStorage backing;
@Before
public void setUp() throws Exception {
backing = new SimpleHttpCacheStorage();
impl = new BasicHttpCache(new HeapResourceFactory(), backing, new CacheConfig());
}
@Test
public void testCanFlushCacheEntriesAtUri() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest req = new HttpDelete("/bar");
final String key = (new URIExtractor()).getURI(host, req);
HttpCacheEntry entry = new CacheEntry();
backing.map.put(key, entry);
impl.flushCacheEntriesFor(host, req);
assertNull(backing.map.get(key));
}
@Test
public void testRecognizesComplete200Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
Resource resource = new HeapResource(bytes);
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testRecognizesComplete206Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
resp.setHeader("Content-Range","bytes 0-127/255");
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testRecognizesIncomplete200Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
assertTrue(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testIgnoresIncompleteNon200Or206Responses()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_FORBIDDEN, "Forbidden");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testResponsesWithoutExplicitContentLengthAreComplete()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testResponsesWithUnparseableContentLengthHeaderAreComplete()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setHeader("Content-Length","foo");
resp.setEntity(new ByteArrayEntity(bytes));
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testIncompleteResponseErrorProvidesPlainTextErrorMessage()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.generateIncompleteResponseError(resp, resource);
Header ctype = result.getFirstHeader("Content-Type");
assertEquals("text/plain;charset=UTF-8", ctype.getValue());
}
@Test
public void testIncompleteResponseErrorProvidesNonEmptyErrorMessage()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.generateIncompleteResponseError(resp, resource);
int clen = Integer.parseInt(result.getFirstHeader("Content-Length").getValue());
assertTrue(clen > 0);
HttpEntity body = result.getEntity();
if (body.getContentLength() < 0) {
InputStream is = body.getContent();
int bytes_read = 0;
while((is.read()) != -1) {
bytes_read++;
}
is.close();
assertEquals(clen, bytes_read);
} else {
assertTrue(body.getContentLength() == clen);
}
}
@Test
public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
final String parentKey = "parentKey";
final String variantKey = "variantKey";
final String existingVariantKey = "existingVariantKey";
final Set<String> existingVariants = new HashSet<String>();
existingVariants.add(existingVariantKey);
final CacheEntry parent = new CacheEntry(existingVariants);
final CacheEntry variant = new CacheEntry();
HttpCacheEntry result = impl.doGetUpdatedParentEntry(parentKey, parent, variant, variantKey);
assertEquals(2, result.getVariantURIs().size());
assertTrue(result.getVariantURIs().contains(existingVariantKey));
assertTrue(result.getVariantURIs().contains(variantKey));
}
@Test
public void testStoreInCachePutsNonVariantEntryInPlace() throws Exception {
CacheEntry entry = new CacheEntry();
assertFalse(entry.hasVariants());
HttpHost host = new HttpHost("foo.example.com");
HttpRequest req = new HttpGet("http://foo.example.com/bar");
String key = (new URIExtractor()).getURI(host, req);
impl.storeInCache(host, req, entry);
assertSame(entry, backing.map.get(key));
}
@Test
public void testTooLargeResponsesAreNotCached() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
Date now = new Date();
Date requestSent = new Date(now.getTime() - 3 * 1000L);
Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
Date responseReceived = new Date(now.getTime() - 1 * 1000L);
HttpResponse originResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES + 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
HttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
assertEquals(0, backing.map.size());
assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
}
@Test
public void testSmallEnoughResponsesAreCached() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
Date now = new Date();
Date requestSent = new Date(now.getTime() - 3 * 1000L);
Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
Date responseReceived = new Date(now.getTime() - 1 * 1000L);
HttpResponse originResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
HttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
assertEquals(1, backing.map.size());
assertTrue(backing.map.containsKey((new URIExtractor()).getURI(host, request)));
assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
}
@Test
public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
HttpCacheEntry result = impl.getCacheEntry(host, request);
Assert.assertNull(result);
}
@Test
public void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception {
CacheEntry entry = new CacheEntry();
assertFalse(entry.hasVariants());
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
String key = (new URIExtractor()).getURI(host, request);
backing.map.put(key,entry);
HttpCacheEntry result = impl.getCacheEntry(host, request);
Assert.assertSame(entry, result);
}
@Test
public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip");
HttpResponse origResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
origResponse.setEntity(HttpTestUtils.makeBody(128));
origResponse.setHeader("Date", DateUtils.formatDate(new Date()));
origResponse.setHeader("Cache-Control", "max-age=3600, public");
origResponse.setHeader("ETag", "\"etag\"");
origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip");
impl.cacheAndReturnResponse(host, origRequest, origResponse, new Date(), new Date());
HttpCacheEntry result = impl.getCacheEntry(host, request);
assertNull(result);
}
@Test
public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest request = new HttpGet("http://foo.example.com/bar");
request.setHeader("Accept-Encoding","gzip");
HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip");
HttpResponse origResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
origResponse.setEntity(HttpTestUtils.makeBody(128));
origResponse.setHeader("Date", DateUtils.formatDate(new Date()));
origResponse.setHeader("Cache-Control", "max-age=3600, public");
origResponse.setHeader("ETag", "\"etag\"");
origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip");
impl.cacheAndReturnResponse(host, origRequest, origResponse, new Date(), new Date());
HttpCacheEntry result = impl.getCacheEntry(host, request);
assertNotNull(result);
}
}

View File

@ -34,7 +34,7 @@ import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.easymock.classextension.EasyMock;
@ -46,7 +46,7 @@ public class TestCacheInvalidator {
private static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
private CacheInvalidator impl;
private HttpCache mockCache;
private HttpCacheStorage mockStorage;
private HttpHost host;
private URIExtractor extractor;
private CacheEntry mockEntry;
@ -54,20 +54,20 @@ public class TestCacheInvalidator {
@Before
public void setUp() {
host = new HttpHost("foo.example.com");
mockCache = EasyMock.createMock(HttpCache.class);
mockStorage = EasyMock.createMock(HttpCacheStorage.class);
extractor = new URIExtractor();
mockEntry = EasyMock.createMock(CacheEntry.class);
impl = new CacheInvalidator(extractor, mockCache);
impl = new CacheInvalidator(extractor, mockStorage);
}
private void replayMocks() {
EasyMock.replay(mockCache);
EasyMock.replay(mockStorage);
EasyMock.replay(mockEntry);
}
private void verifyMocks() {
EasyMock.verify(mockCache);
EasyMock.verify(mockStorage);
EasyMock.verify(mockEntry);
}
@ -283,16 +283,16 @@ public class TestCacheInvalidator {
}
private void cacheReturnsEntryForUri(String theUri) throws IOException {
org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andReturn(mockEntry);
org.easymock.EasyMock.expect(mockStorage.getEntry(theUri)).andReturn(mockEntry);
}
private void cacheReturnsExceptionForUri(String theUri) throws IOException {
org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andThrow(
org.easymock.EasyMock.expect(mockStorage.getEntry(theUri)).andThrow(
new IOException("TOTAL FAIL"));
}
private void entryIsRemoved(String theUri) throws IOException {
mockCache.removeEntry(theUri);
mockStorage.removeEntry(theUri);
}
}

View File

@ -27,14 +27,11 @@
package org.apache.http.impl.client.cache;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@ -47,13 +44,9 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.Resource;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
@ -73,17 +66,9 @@ public class TestCachingHttpClient {
private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
private static final String GET_CACHE_ENTRY = "getCacheEntry";
private static final String STORE_IN_CACHE = "storeInCache";
private static final String GET_RESPONSE_READER = "getResponseReader";
private CachingHttpClient impl;
private boolean mockedImpl;
private ResourceFactory mockResourceFactory;
private CacheInvalidator mockInvalidator;
private CacheValidityPolicy mockValidityPolicy;
private CacheableRequestPolicy mockRequestPolicy;
private HttpClient mockBackend;
@ -92,20 +77,14 @@ public class TestCachingHttpClient {
private ResponseCachingPolicy mockResponsePolicy;
private HttpResponse mockBackendResponse;
private CacheEntry mockCacheEntry;
private CacheEntry mockVariantCacheEntry;
private CacheEntry mockUpdatedCacheEntry;
private URIExtractor mockExtractor;
private CachedHttpResponseGenerator mockResponseGenerator;
private SizeLimitedResponseReader mockResponseReader;
private ClientConnectionManager mockConnectionManager;
private ResponseHandler<Object> mockHandler;
private HttpUriRequest mockUriRequest;
private HttpResponse mockCachedResponse;
private HttpResponse mockReconstructedResponse;
private ConditionalRequestBuilder mockConditionalRequestBuilder;
private HttpRequest mockConditionalRequest;
private StatusLine mockStatusLine;
private CacheEntryUpdater mockCacheEntryUpdater;
private ResponseProtocolCompliance mockResponseProtocolCompliance;
private RequestProtocolCompliance mockRequestProtocolCompliance;
@ -119,8 +98,6 @@ public class TestCachingHttpClient {
@SuppressWarnings("unchecked")
@Before
public void setUp() {
mockResourceFactory = EasyMock.createMock(ResourceFactory.class);
mockInvalidator = EasyMock.createMock(CacheInvalidator.class);
mockRequestPolicy = EasyMock.createMock(CacheableRequestPolicy.class);
mockValidityPolicy = EasyMock.createMock(CacheValidityPolicy.class);
mockBackend = EasyMock.createMock(HttpClient.class);
@ -132,17 +109,11 @@ public class TestCachingHttpClient {
mockBackendResponse = EasyMock.createMock(HttpResponse.class);
mockUriRequest = EasyMock.createMock(HttpUriRequest.class);
mockCacheEntry = EasyMock.createMock(CacheEntry.class);
mockUpdatedCacheEntry = EasyMock.createMock(CacheEntry.class);
mockVariantCacheEntry = EasyMock.createMock(CacheEntry.class);
mockExtractor = EasyMock.createMock(URIExtractor.class);
mockResponseGenerator = EasyMock.createMock(CachedHttpResponseGenerator.class);
mockCachedResponse = EasyMock.createMock(HttpResponse.class);
mockConditionalRequestBuilder = EasyMock.createMock(ConditionalRequestBuilder.class);
mockConditionalRequest = EasyMock.createMock(HttpRequest.class);
mockStatusLine = EasyMock.createMock(StatusLine.class);
mockCacheEntryUpdater = EasyMock.createMock(CacheEntryUpdater.class);
mockResponseReader = EasyMock.createMock(SizeLimitedResponseReader.class);
mockReconstructedResponse = EasyMock.createMock(HttpResponse.class);
mockResponseProtocolCompliance = EasyMock.createMock(ResponseProtocolCompliance.class);
mockRequestProtocolCompliance = EasyMock.createMock(RequestProtocolCompliance.class);
@ -154,32 +125,24 @@ public class TestCachingHttpClient {
params = new BasicHttpParams();
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance);
}
private void replayMocks() {
EasyMock.replay(mockResourceFactory);
EasyMock.replay(mockInvalidator);
EasyMock.replay(mockRequestPolicy);
EasyMock.replay(mockValidityPolicy);
EasyMock.replay(mockSuitabilityChecker);
EasyMock.replay(mockResponsePolicy);
EasyMock.replay(mockCacheEntry);
EasyMock.replay(mockVariantCacheEntry);
EasyMock.replay(mockResponseGenerator);
EasyMock.replay(mockExtractor);
EasyMock.replay(mockBackend);
EasyMock.replay(mockCache);
EasyMock.replay(mockConnectionManager);
@ -190,9 +153,6 @@ public class TestCachingHttpClient {
EasyMock.replay(mockConditionalRequestBuilder);
EasyMock.replay(mockConditionalRequest);
EasyMock.replay(mockStatusLine);
EasyMock.replay(mockCacheEntryUpdater);
EasyMock.replay(mockResponseReader);
EasyMock.replay(mockReconstructedResponse);
EasyMock.replay(mockResponseProtocolCompliance);
EasyMock.replay(mockRequestProtocolCompliance);
if (mockedImpl) {
@ -201,16 +161,12 @@ public class TestCachingHttpClient {
}
private void verifyMocks() {
EasyMock.verify(mockResourceFactory);
EasyMock.verify(mockInvalidator);
EasyMock.verify(mockRequestPolicy);
EasyMock.verify(mockValidityPolicy);
EasyMock.verify(mockSuitabilityChecker);
EasyMock.verify(mockResponsePolicy);
EasyMock.verify(mockCacheEntry);
EasyMock.verify(mockVariantCacheEntry);
EasyMock.verify(mockResponseGenerator);
EasyMock.verify(mockExtractor);
EasyMock.verify(mockBackend);
EasyMock.verify(mockCache);
EasyMock.verify(mockConnectionManager);
@ -221,9 +177,6 @@ public class TestCachingHttpClient {
EasyMock.verify(mockConditionalRequestBuilder);
EasyMock.verify(mockConditionalRequest);
EasyMock.verify(mockStatusLine);
EasyMock.verify(mockCacheEntryUpdater);
EasyMock.verify(mockResponseReader);
EasyMock.verify(mockReconstructedResponse);
EasyMock.verify(mockResponseProtocolCompliance);
EasyMock.verify(mockRequestProtocolCompliance);
if (mockedImpl) {
@ -233,20 +186,12 @@ public class TestCachingHttpClient {
@Test
public void testCacheableResponsesGoIntoCache() throws Exception {
mockImplMethods(STORE_IN_CACHE, GET_RESPONSE_READER);
responsePolicyAllowsCaching(true);
responseProtocolValidationIsCalled();
getMockResponseReader();
responseRead();
responseLimitReached(false);
responseGetResource();
storeInCacheWasCalled();
responseIsGeneratedFromCache();
responseStatusLineIsInspectable();
responseGetHeaders();
responseDoesNotHaveExplicitContentLength();
EasyMock.expect(mockCache.cacheAndReturnResponse(host, request, mockBackendResponse, requestDate, responseDate))
.andReturn(mockCachedResponse);
replayMocks();
HttpResponse result = impl.handleBackendResponse(host, request, requestDate,
@ -283,37 +228,13 @@ public class TestCachingHttpClient {
errors);
}
@Test
public void testStoreInCachePutsNonVariantEntryInPlace() throws Exception {
final String theURI = "theURI";
cacheEntryHasVariants(false);
extractTheURI(theURI);
putInCache(theURI);
replayMocks();
impl.storeInCache(host, request, mockCacheEntry);
verifyMocks();
}
@Test
public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
final String variantURI = "variantURI";
final CacheEntry entry = new CacheEntry();
copyResource();
replayMocks();
impl.doGetUpdatedParentEntry("/stuff", null, entry, variantURI);
verifyMocks();
}
@Test
public void testCacheMissCausesBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND);
mockImplMethods(CALL_BACKEND);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
getCacheEntryReturns(null);
requestProtocolValidationIsCalled();
requestIsFatallyNonCompliant(null);
@ -331,7 +252,7 @@ public class TestCachingHttpClient {
@Test
public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND);
mockImplMethods(CALL_BACKEND);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled();
@ -354,7 +275,7 @@ public class TestCachingHttpClient {
@Test
public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, REVALIDATE_CACHE_ENTRY);
mockImplMethods(REVALIDATE_CACHE_ENTRY);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled();
@ -384,11 +305,9 @@ public class TestCachingHttpClient {
backendCallWasMadeWithRequest(mockConditionalRequest);
getCurrentDateReturns(responseDate);
backendResponseCodeIs(HttpStatus.SC_OK);
cacheEntryUpdaterCalled();
cacheEntryHasVariants(false, mockUpdatedCacheEntry);
extractTheURI("http://foo.example.com");
putInCache("http://foo.example.com", mockUpdatedCacheEntry);
responseIsGeneratedFromCache(mockUpdatedCacheEntry);
EasyMock.expect(mockCache.updateCacheEntry(host, request,
mockCacheEntry, mockBackendResponse, requestDate, responseDate))
.andReturn(mockCachedResponse);
replayMocks();
@ -406,17 +325,15 @@ public class TestCachingHttpClient {
@Test
public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse()
throws Exception {
mockImplMethods(GET_CURRENT_DATE, STORE_IN_CACHE);
mockImplMethods(GET_CURRENT_DATE);
conditionalRequestBuilderCalled();
getCurrentDateReturns(requestDate);
backendCallWasMadeWithRequest(mockConditionalRequest);
getCurrentDateReturns(responseDate);
backendResponseCodeIs(HttpStatus.SC_NOT_MODIFIED);
cacheEntryUpdaterCalled();
storeInCacheWasCalled(mockUpdatedCacheEntry);
responseIsGeneratedFromCache(mockUpdatedCacheEntry);
EasyMock.expect(mockCache.updateCacheEntry(host, request,
mockCacheEntry, mockBackendResponse, requestDate, responseDate))
.andReturn(mockCachedResponse);
replayMocks();
@ -432,7 +349,6 @@ public class TestCachingHttpClient {
@Test
public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY);
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled();
@ -465,13 +381,11 @@ public class TestCachingHttpClient {
@Test
public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
final String theURI = "theURI";
Date currentDate = new Date();
responsePolicyAllowsCaching(false);
responseProtocolValidationIsCalled();
extractTheURI(theURI);
removeFromCache(theURI);
flushCache();
replayMocks();
HttpResponse result = impl.handleBackendResponse(host, request, currentDate,
@ -481,115 +395,6 @@ public class TestCachingHttpClient {
Assert.assertSame(mockBackendResponse, result);
}
@Test
public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
final String theURI = "theURI";
extractTheURI(theURI);
gotCacheMiss(theURI);
replayMocks();
HttpCacheEntry result = impl.getCacheEntry(host, request);
verifyMocks();
Assert.assertNull(result);
}
@Test
public void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception {
final String theURI = "theURI";
extractTheURI(theURI);
gotCacheHit(theURI);
cacheEntryHasVariants(false);
replayMocks();
HttpCacheEntry result = impl.getCacheEntry(host, request);
verifyMocks();
Assert.assertSame(mockCacheEntry, result);
}
@Test
public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
final String theURI = "theURI";
final String variantURI = "variantURI";
extractTheURI(theURI);
gotCacheHit(theURI);
cacheEntryHasVariants(true);
extractVariantURI(variantURI);
gotCacheMiss(variantURI);
replayMocks();
HttpCacheEntry result = impl.getCacheEntry(host, request);
verifyMocks();
Assert.assertNull(result);
}
@Test
public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
final String theURI = "theURI";
final String variantURI = "variantURI";
extractTheURI(theURI);
gotCacheHit(theURI, mockCacheEntry);
cacheEntryHasVariants(true);
extractVariantURI(variantURI);
gotCacheHit(variantURI, mockVariantCacheEntry);
replayMocks();
HttpCacheEntry result = impl.getCacheEntry(host, request);
verifyMocks();
Assert.assertSame(mockVariantCacheEntry, result);
}
@Test
public void testTooLargeResponsesAreNotCached() throws Exception {
mockImplMethods(GET_CURRENT_DATE, GET_RESPONSE_READER, STORE_IN_CACHE);
getCurrentDateReturns(requestDate);
backendCallWasMadeWithRequest(request);
responseProtocolValidationIsCalled();
getCurrentDateReturns(responseDate);
responsePolicyAllowsCaching(true);
getMockResponseReader();
responseRead();
responseLimitReached(true);
responseGetReconstructed();
replayMocks();
impl.callBackend(host, request, context);
verifyMocks();
}
@Test
public void testSmallEnoughResponsesAreCached() throws Exception {
requestDate = new Date();
responseDate = new Date();
mockImplMethods(GET_CURRENT_DATE, GET_RESPONSE_READER, STORE_IN_CACHE);
getCurrentDateReturns(requestDate);
responseProtocolValidationIsCalled();
backendCallWasMadeWithRequest(request);
getCurrentDateReturns(responseDate);
responsePolicyAllowsCaching(true);
getMockResponseReader();
responseRead();
responseLimitReached(false);
responseGetResource();
storeInCacheWasCalled();
responseIsGeneratedFromCache();
responseStatusLineIsInspectable();
responseGetHeaders();
responseDoesNotHaveExplicitContentLength();
replayMocks();
impl.callBackend(host, request, context);
verifyMocks();
}
@Test
public void testCallsSelfForExecuteOnHostRequestWithNullContext() throws Exception {
@ -599,17 +404,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -641,17 +442,13 @@ public class TestCachingHttpClient {
final Object value = new Object();
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -691,17 +488,13 @@ public class TestCachingHttpClient {
final HttpContext theContext = context;
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -733,17 +526,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -773,17 +562,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -816,17 +601,13 @@ public class TestCachingHttpClient {
final Object theValue = new Object();
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -861,17 +642,13 @@ public class TestCachingHttpClient {
final Object theValue = new Object();
impl = new CachingHttpClient(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance) {
@Override
@ -916,17 +693,13 @@ public class TestCachingHttpClient {
@Test
public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception {
final String theURI = "http://foo";
requestIsFatallyNonCompliant(null);
requestProtocolValidationIsCalled();
cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true);
cacheEntrySuitable(true);
extractTheURI(theURI);
gotCacheHit(theURI);
getCacheEntryReturns(mockCacheEntry);
responseIsGeneratedFromCache();
cacheEntryHasVariants(false);
replayMocks();
impl.execute(host, request, context);
@ -953,123 +726,17 @@ public class TestCachingHttpClient {
Assert.assertTrue(gotException);
}
@Test
public void testCorrectIncompleteResponseDoesNotCorrectComplete200Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result));
}
@Test
public void testCorrectIncompleteResponseDoesNotCorrectComplete206Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
resp.setHeader("Content-Range","bytes 0-127/255");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result));
}
@Test
public void testCorrectIncompleteResponseGenerates502ForIncomplete200Response()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpStatus.SC_BAD_GATEWAY == result.getStatusLine().getStatusCode());
}
@Test
public void testCorrectIncompleteResponseDoesNotCorrectIncompleteNon200Or206Responses()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_FORBIDDEN, "Forbidden");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result));
}
@Test
public void testCorrectIncompleteResponseDoesNotCorrectResponsesWithoutExplicitContentLength()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result));
}
@Test
public void testCorrectIncompleteResponseDoesNotCorrectResponsesWithUnparseableContentLengthHeader()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setHeader("Content-Length","foo");
resp.setEntity(new ByteArrayEntity(bytes));
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Assert.assertTrue(HttpTestUtils.semanticallyTransparent(resp, result));
}
@Test
public void testCorrectIncompleteResponseProvidesPlainTextErrorMessage()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
Header ctype = result.getFirstHeader("Content-Type");
Assert.assertEquals("text/plain;charset=UTF-8", ctype.getValue());
}
@Test
public void testCorrectIncompleteResponseProvidesNonEmptyErrorMessage()
throws Exception {
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
HttpResponse result = impl.correctIncompleteResponse(resp, new HeapResource(bytes));
int clen = Integer.parseInt(result.getFirstHeader("Content-Length").getValue());
Assert.assertTrue(clen > 0);
HttpEntity body = result.getEntity();
if (body.getContentLength() < 0) {
InputStream is = body.getContent();
int bytes_read = 0;
while((is.read()) != -1) {
bytes_read++;
}
is.close();
Assert.assertEquals(clen, bytes_read);
} else {
Assert.assertTrue(body.getContentLength() == clen);
}
}
@Test
public void testIsSharedCache() {
Assert.assertTrue(impl.isSharedCache());
}
private void getCacheEntryReturns(HttpCacheEntry result) throws IOException {
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(result);
}
private void cacheInvalidatorWasCalled() throws IOException {
mockInvalidator.flushInvalidatedCacheEntries(
mockCache.flushInvalidatedCacheEntriesFor(
EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject());
}
@ -1096,22 +763,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpCacheEntry>anyObject())).andReturn(b);
}
private void cacheEntryUpdaterCalled() throws IOException {
EasyMock.expect(
mockCacheEntryUpdater.updateCacheEntry(
EasyMock.<String>anyObject(),
EasyMock.<HttpCacheEntry>anyObject(),
EasyMock.<Date>anyObject(),
EasyMock.<Date>anyObject(),
EasyMock.<HttpResponse>anyObject())).andReturn(mockUpdatedCacheEntry);
}
private void getCacheEntryReturns(CacheEntry entry) throws IOException {
EasyMock.expect(impl.getCacheEntry(
EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject())).andReturn(entry);
}
private void backendResponseCodeIs(int code) {
EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(mockStatusLine);
EasyMock.expect(mockStatusLine.getStatusCode()).andReturn(code);
@ -1128,42 +779,11 @@ public class TestCachingHttpClient {
EasyMock.expect(impl.getCurrentDate()).andReturn(date);
}
private void getMockResponseReader() {
EasyMock.expect(impl.getResponseReader(
EasyMock.<HttpRequest>anyObject(),
EasyMock.<HttpResponse>anyObject())).andReturn(mockResponseReader);
}
private void removeFromCache(String theURI) throws Exception {
mockCache.removeEntry(theURI);
}
private void requestPolicyAllowsCaching(boolean allow) {
EasyMock.expect(mockRequestPolicy.isServableFromCache(
EasyMock.<HttpRequest>anyObject())).andReturn(allow);
}
private void responseDoesNotHaveExplicitContentLength() {
EasyMock.expect(mockBackendResponse.getFirstHeader("Content-Length"))
.andReturn(null).anyTimes();
}
private void responseRead() throws Exception {
mockResponseReader.readResponse();
}
private void responseLimitReached(boolean limitReached) throws Exception {
EasyMock.expect(mockResponseReader.isLimitReached()).andReturn(limitReached);
}
private void responseGetResource() throws Exception {
EasyMock.expect(mockResponseReader.getResource()).andReturn(new HeapResource(new byte[] {} ));
}
private void responseGetReconstructed() throws Exception {
EasyMock.expect(mockResponseReader.getReconstructedResponse()).andReturn(mockReconstructedResponse);
}
private void backendCallWasMadeWithRequest(HttpRequest request) throws IOException {
EasyMock.expect(mockBackend.execute(
EasyMock.<HttpHost>anyObject(),
@ -1178,10 +798,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpResponse>anyObject())).andReturn(allow);
}
private void gotCacheMiss(String theURI) throws Exception {
EasyMock.expect(mockCache.getEntry(theURI)).andReturn(null);
}
private void cacheEntrySuitable(boolean suitable) {
EasyMock.expect(
mockSuitabilityChecker.canCachedResponseBeUsed(
@ -1190,68 +806,13 @@ public class TestCachingHttpClient {
EasyMock.<HttpCacheEntry>anyObject())).andReturn(suitable);
}
private void gotCacheHit(String theURI) throws Exception {
EasyMock.expect(mockCache.getEntry(theURI)).andReturn(mockCacheEntry);
}
private void gotCacheHit(String theURI, CacheEntry entry) throws Exception {
EasyMock.expect(mockCache.getEntry(theURI)).andReturn(entry);
}
private void cacheEntryHasVariants(boolean b) {
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(b);
}
private void cacheEntryHasVariants(boolean b, CacheEntry entry) {
EasyMock.expect(entry.hasVariants()).andReturn(b);
}
private void responseIsGeneratedFromCache() {
EasyMock.expect(mockResponseGenerator.generateResponse(
EasyMock.<HttpCacheEntry>anyObject())).andReturn(mockCachedResponse);
}
private void responseStatusLineIsInspectable() {
EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(new OKStatus()).anyTimes();
}
private void responseGetHeaders() {
EasyMock.expect(mockBackendResponse.getAllHeaders()).andReturn(new Header[] {}).anyTimes();
}
private void responseIsGeneratedFromCache(CacheEntry entry) {
EasyMock.expect(mockResponseGenerator.generateResponse(entry))
.andReturn(mockCachedResponse);
}
private void extractTheURI(String theURI) {
EasyMock.expect(mockExtractor.getURI(host, request)).andReturn(theURI);
}
private void extractVariantURI(String variantURI) {
extractVariantURI(variantURI,mockCacheEntry);
}
private void extractVariantURI(String variantURI, CacheEntry entry){
EasyMock.expect(mockExtractor.getVariantURI(
EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject(),
EasyMock.same(entry))).andReturn(variantURI);
}
private void putInCache(String theURI) throws Exception {
mockCache.putEntry(theURI, mockCacheEntry);
}
private void putInCache(String theURI, CacheEntry entry) throws Exception {
mockCache.putEntry(theURI, entry);
}
private void copyResource() throws IOException {
EasyMock.expect(
mockResourceFactory.copy(
EasyMock.<String>anyObject(),
EasyMock.<Resource>anyObject())).andReturn(new HeapResource(new byte[] {}));
private void flushCache() throws IOException {
mockCache.flushCacheEntriesFor(host, request);
}
private void handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response)
@ -1265,20 +826,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpResponse>anyObject())).andReturn(response);
}
private void storeInCacheWasCalled() throws IOException {
impl.storeInCache(
EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject(),
EasyMock.<HttpCacheEntry>anyObject());
}
private void storeInCacheWasCalled(CacheEntry entry) throws IOException {
impl.storeInCache(
EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject(),
EasyMock.same(entry));
}
private void responseProtocolValidationIsCalled() throws ClientProtocolException {
mockResponseProtocolCompliance.ensureProtocolCompliance(
EasyMock.<HttpRequest>anyObject(),
@ -1301,17 +848,13 @@ public class TestCachingHttpClient {
mockedImpl = true;
impl = EasyMock.createMockBuilder(CachingHttpClient.class).withConstructor(
mockBackend,
mockResourceFactory,
mockValidityPolicy,
mockResponsePolicy,
mockExtractor,
mockCache,
mockResponseGenerator,
mockInvalidator,
mockRequestPolicy,
mockSuitabilityChecker,
mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance,
mockRequestProtocolCompliance).addMockedMethods(methods).createMock();
}

View File

@ -93,14 +93,16 @@ public class TestProtocolDeviations {
originResponse = make200Response();
HttpCache cache = new BasicHttpCache(MAX_ENTRIES);
CacheConfig params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
params.setMaxCacheEntries(MAX_ENTRIES);
HttpCache cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockEntity = EasyMock.createMock(HttpEntity.class);
mockCache = EasyMock.createMock(HttpCache.class);
CacheConfig params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, cache, params);
}
private HttpResponse make200Response() {

View File

@ -42,7 +42,6 @@ import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.RequestWrapper;
@ -2220,9 +2219,9 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
mockCache.putEntry(EasyMock.eq("http://foo.example.com/thing"), EasyMock.isA(HttpCacheEntry.class));
impl = new CachingHttpClient(mockBackend, mockCache, params);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpRequest validate = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
validate.setHeader("If-None-Match", "\"etag\"");
@ -2232,17 +2231,23 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
notModified.setHeader("Date", DateUtils.formatDate(now));
notModified.setHeader("ETag", "\"etag\"");
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry);
HttpResponse reconstructed = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
EasyMock.expect(
mockBackend.execute(EasyMock.eq(host), eqRequest(validate), (HttpContext) EasyMock
.isNull())).andReturn(notModified);
EasyMock.expect(mockCache.updateCacheEntry(EasyMock.same(host), EasyMock.same(request),
EasyMock.same(entry), EasyMock.same(notModified), EasyMock.isA(Date.class),
EasyMock.isA(Date.class)))
.andReturn(reconstructed);
replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request);
verifyMocks();
Assert.assertEquals(200, result.getStatusLine().getStatusCode());
Assert.assertSame(reconstructed, result);
}
@Test
@ -2264,12 +2269,13 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, mockCache, params);
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry);
mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request);
verifyMocks();
@ -2305,16 +2311,17 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, mockCache, params);
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry);
mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
EasyMock.expect(
mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class),
(HttpContext) EasyMock.isNull())).andThrow(
new IOException("can't talk to origin!")).anyTimes();
replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request);
@ -2506,12 +2513,13 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, mockCache, params);
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry);
mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request);
verifyMocks();
@ -2550,7 +2558,9 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(requestTime, responseTime, hdrs, bytes);
impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
impl = new CachingHttpClient(mockBackend, mockCache, params);
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse validated = make200Response();
validated.setHeader("Cache-Control", "public");
@ -2558,18 +2568,20 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
validated.setHeader("Content-Length", "128");
validated.setEntity(new ByteArrayEntity(bytes));
HttpResponse reconstructed = make200Response();
Capture<HttpRequest> cap = new Capture<HttpRequest>();
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry);
mockCache.putEntry(EasyMock.isA(String.class), EasyMock.isA(HttpCacheEntry.class));
mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
EasyMock.expect(
mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.capture(cap),
(HttpContext) EasyMock.isNull())).andReturn(validated).times(0, 1);
EasyMock.expect(mockCache.updateCacheEntry(EasyMock.same(host), EasyMock.same(request), EasyMock.same(entry),
EasyMock.same(validated), EasyMock.isA(Date.class), EasyMock.isA(Date.class)))
.andReturn(reconstructed).times(0, 1);
replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request);
verifyMocks();

View File

@ -1,171 +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.ByteArrayOutputStream;
import java.io.InputStream;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TestResponseCache {
private BasicHttpCache cache;
private HttpCacheEntry entry;
@Before
public void setUp() {
cache = new BasicHttpCache(5);
entry = new CacheEntry();
}
@Test
public void testEntryRemainsInCacheWhenPutThere() throws Exception {
cache.putEntry("foo", entry);
HttpCacheEntry cachedEntry = cache.getEntry("foo");
Assert.assertSame(entry, cachedEntry);
}
@Test
public void testRemovedEntriesDoNotExistAnymore() throws Exception {
cache.putEntry("foo", entry);
cache.removeEntry("foo");
HttpCacheEntry nullEntry = cache.getEntry("foo");
Assert.assertNull(nullEntry);
}
@Test
public void testCacheHoldsNoMoreThanSpecifiedMaxEntries() throws Exception {
BasicHttpCache cache = new BasicHttpCache(1);
HttpCacheEntry entry1 = new CacheEntry();
cache.putEntry("foo", entry1);
HttpCacheEntry entry2 = new CacheEntry();
cache.putEntry("bar", entry2);
HttpCacheEntry entry3 = new CacheEntry();
cache.putEntry("baz", entry3);
HttpCacheEntry e1 = cache.getEntry("foo");
Assert.assertNull("Got foo entry when we should not", e1);
HttpCacheEntry e2 = cache.getEntry("bar");
Assert.assertNull("Got bar entry when we should not", e2);
HttpCacheEntry e3 = cache.getEntry("baz");
Assert.assertNotNull("Did not get baz entry, but should have", e3);
}
@Test
public void testSmallCacheKeepsMostRecentlyUsedEntry() throws Exception {
final int max_size = 3;
BasicHttpCache cache = new BasicHttpCache(max_size);
// fill the cache with entries
for (int i = 0; i < max_size; i++) {
HttpCacheEntry entry = new CacheEntry();
cache.putEntry("entry" + i, entry);
}
// read the eldest entry to make it the MRU entry
cache.getEntry("entry0");
// add another entry, which kicks out the eldest (should be the 2nd one
// created), and becomes the new MRU entry
HttpCacheEntry newMru = new CacheEntry();
cache.putEntry("newMru", newMru);
// get the original second eldest
HttpCacheEntry gone = cache.getEntry("entry1");
Assert.assertNull("entry1 should be gone", gone);
HttpCacheEntry latest = cache.getEntry("newMru");
Assert.assertNotNull("latest entry should still be there", latest);
HttpCacheEntry originalEldest = cache.getEntry("entry0");
Assert.assertNotNull("original eldest entry should still be there", originalEldest);
}
@Test
public void testZeroMaxSizeCacheDoesNotStoreAnything() throws Exception {
BasicHttpCache cache = new BasicHttpCache(0);
HttpCacheEntry entry = new CacheEntry();
cache.putEntry("foo", entry);
HttpCacheEntry gone = cache.getEntry("foo");
Assert.assertNull("This cache should not have anything in it!", gone);
}
@Test
public void testCacheEntryCallbackUpdatesCacheEntry() throws Exception {
final byte[] expectedArray = new byte[] { 1, 2, 3, 4, 5 };
HttpCacheEntry entry = new CacheEntry();
cache.putEntry("foo", entry);
cache.updateEntry("foo", new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry existing) {
HttpCacheEntry updated = new HttpCacheEntry(
existing.getRequestDate(),
existing.getRequestDate(),
existing.getStatusLine(),
existing.getAllHeaders(),
new HeapResource(expectedArray),
null);
return updated;
}
});
HttpCacheEntry afterUpdate = cache.getEntry("foo");
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
InputStream instream = afterUpdate.getResource().getInputStream();
byte[] buf = new byte[2048];
int len;
while ((len = instream.read(buf)) != -1) {
outstream.write(buf, 0, len);
}
byte[] bytes = outstream.toByteArray();
Assert.assertArrayEquals(expectedArray, bytes);
}
}

View File

@ -26,18 +26,14 @@
*/
package org.apache.http.impl.client.cache;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
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.message.BasicRequestLine;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.util.EntityUtils;
import org.easymock.classextension.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -46,56 +42,44 @@ public class TestSizeLimitedResponseReader {
private static final long MAX_SIZE = 4;
private HttpRequest request;
private SizeLimitedResponseReader impl;
private HttpRequest mockRequest;
private HttpResponse mockResponse;
private HttpEntity mockEntity;
private boolean mockedImpl;
@Before
public void setUp() {
mockRequest = EasyMock.createMock(HttpRequest.class);
mockResponse = EasyMock.createMock(HttpResponse.class);
mockEntity = EasyMock.createMock(HttpEntity.class);
request = new HttpGet("http://foo.example.com/bar");
}
@Test
public void testLargeResponseIsTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3, 4, 5};
requestReturnsRequestLine();
responseReturnsProtocolVersion();
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf));
initReader();
replayMocks();
byte[] buf = new byte[] { 1, 2, 3, 4, 5 };
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.setEntity(new ByteArrayEntity(buf));
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
impl.readResponse();
boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity());
HttpResponse result = impl.getReconstructedResponse();
byte[] body = EntityUtils.toByteArray(result.getEntity());
verifyMocks();
Assert.assertTrue(tooLarge);
Assert.assertArrayEquals(buf, result);
Assert.assertArrayEquals(buf, body);
}
@Test
public void testExactSizeResponseIsNotTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3, 4 };
requestReturnsRequestLine();
responseReturnsProtocolVersion();
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf));
initReader();
replayMocks();
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.setEntity(new ByteArrayEntity(buf));
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
impl.readResponse();
boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity());
HttpResponse reconstructed = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
verifyMocks();
Assert.assertFalse(tooLarge);
Assert.assertArrayEquals(buf, result);
}
@ -103,18 +87,15 @@ public class TestSizeLimitedResponseReader {
@Test
public void testSmallResponseIsNotTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3 };
requestReturnsRequestLine();
responseReturnsProtocolVersion();
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf));
initReader();
replayMocks();
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.setEntity(new ByteArrayEntity(buf));
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
impl.readResponse();
boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity());
verifyMocks();
HttpResponse reconstructed = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
Assert.assertFalse(tooLarge);
Assert.assertArrayEquals(buf, result);
@ -122,56 +103,14 @@ public class TestSizeLimitedResponseReader {
@Test
public void testResponseWithNoEntityIsNotTooLarge() throws Exception {
responseHasNullEntity();
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
initReader();
replayMocks();
impl.readResponse();
boolean tooLarge = impl.isLimitReached();
verifyMocks();
Assert.assertFalse(tooLarge);
}
private void responseReturnsContent(InputStream buffer) throws IOException {
EasyMock.expect(mockResponse.getEntity()).andReturn(mockEntity);
EasyMock.expect(mockEntity.getContent()).andReturn(buffer);
}
private void requestReturnsRequestLine() {
EasyMock.expect(mockRequest.getRequestLine()).andReturn(
new BasicRequestLine("GET", "/", HttpVersion.HTTP_1_1));
}
private void responseReturnsProtocolVersion() {
EasyMock.expect(mockResponse.getProtocolVersion()).andReturn(HttpVersion.HTTP_1_1);
}
private void responseReturnsHeaders() {
EasyMock.expect(mockResponse.getAllHeaders()).andReturn(new Header[] {});
}
private void responseHasNullEntity() {
EasyMock.expect(mockResponse.getEntity()).andReturn(null);
}
private void verifyMocks() {
EasyMock.verify(mockRequest, mockResponse, mockEntity);
if (mockedImpl) {
EasyMock.verify(impl);
}
}
private void replayMocks() {
EasyMock.replay(mockRequest, mockResponse, mockEntity);
if (mockedImpl) {
EasyMock.replay(impl);
}
}
private void initReader() {
impl = new SizeLimitedResponseReader(
new HeapResourceFactory(), MAX_SIZE, mockRequest, mockResponse);
}
}

View File

@ -40,11 +40,11 @@ import org.junit.Test;
public class TestEhcacheHttpCache extends TestCase {
private Ehcache mockCache;
private EhcacheHttpCache impl;
private EhcacheHttpCacheStorage impl;
public void setUp() {
mockCache = EasyMock.createMock(Ehcache.class);
impl = new EhcacheHttpCache(mockCache);
impl = new EhcacheHttpCacheStorage(mockCache);
}
@Test

View File

@ -1,99 +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.ehcache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.apache.http.HttpHost;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClient;
import org.apache.http.impl.client.cache.HeapResourceFactory;
import org.apache.http.impl.client.cache.HttpTestUtils;
import org.apache.http.impl.client.cache.TestProtocolRequirements;
import org.apache.http.message.BasicHttpRequest;
import org.easymock.classextension.EasyMock;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
public class TestEhcacheProtcolRequirements extends TestProtocolRequirements{
private final String TEST_EHCACHE_NAME = "TestEhcacheProtocolRequirements-cache";
private static CacheManager CACHE_MANAGER;
@BeforeClass
public static void setUpGlobal() {
Configuration config = new Configuration();
config.addDefaultCache(
new CacheConfiguration("default", Integer.MAX_VALUE)
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
.overflowToDisk(false));
CACHE_MANAGER = CacheManager.create(config);
}
@Override
@Before
public void setUp() {
host = new HttpHost("foo.example.com");
body = HttpTestUtils.makeBody(entityLength);
request = new BasicHttpRequest("GET", "/foo", HttpVersion.HTTP_1_1);
originResponse = make200Response();
if (CACHE_MANAGER.cacheExists(TEST_EHCACHE_NAME)){
CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
}
CACHE_MANAGER.addCache(TEST_EHCACHE_NAME);
cache = new EhcacheHttpCache(CACHE_MANAGER.getCache(TEST_EHCACHE_NAME));
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
params = new CacheConfig();
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
}
@After
public void tearDown(){
CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
}
@AfterClass
public static void tearDownGlobal(){
CACHE_MANAGER.shutdown();
}
}