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; package org.apache.http.client.cache;
import java.io.IOException; 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 * @since 4.1
*/ */
public interface HttpCache { 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( HttpResponse cacheAndReturnResponse(
String key, HttpCacheUpdateCallback callback) throws IOException; 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; package org.apache.http.impl.client.cache;
import java.io.IOException; 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.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry; 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.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 { 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) { public BasicHttpCache(ResourceFactory resourceFactory, HttpCacheStorage storage, CacheConfig config) {
this.entries = new CacheMap(maxEntries); 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);
} }
/** public BasicHttpCache(CacheConfig config) {
* Places a HttpCacheEntry in the cache this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config);
*
* @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() {
* Gets an entry from the cache, if it exists this(new CacheConfig());
*
* @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 void flushCacheEntriesFor(HttpHost host, HttpRequest request)
* Removes a HttpCacheEntry from the cache throws IOException {
* String uri = uriExtractor.getURI(host, request);
* @param url storage.removeEntry(uri);
* Url that is the cache key
*/
public synchronized void removeEntry(String url) throws IOException {
entries.remove(url);
} }
public synchronized void updateEntry( void storeInCache(
String url, HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
HttpCacheUpdateCallback callback) throws IOException { if (entry.hasVariants()) {
HttpCacheEntry existingEntry = entries.get(url); storeVariantEntry(target, request, entry);
entries.put(url, callback.update(existingEntry)); } 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; 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 maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
private boolean isSharedCache = true; private boolean isSharedCache = true;
/** /**
@ -74,4 +81,19 @@ public class CacheConfig {
this.isSharedCache = isSharedCache; 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.HeaderConstants;
import org.apache.http.client.cache.HttpCache; import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry; 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 * 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 @ThreadSafe // so long as the cache implementation is thread-safe
class CacheInvalidator { class CacheInvalidator {
private final HttpCache cache; private final HttpCacheStorage storage;
private final URIExtractor uriExtractor; private final URIExtractor uriExtractor;
private final Log log = LogFactory.getLog(getClass()); private final Log log = LogFactory.getLog(getClass());
@ -60,13 +61,13 @@ class CacheInvalidator {
* {@link URIExtractor}. * {@link URIExtractor}.
* *
* @param uriExtractor Provides identifiers for the keys to store cache entries * @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( public CacheInvalidator(
final URIExtractor uriExtractor, final URIExtractor uriExtractor,
final HttpCache cache) { final HttpCacheStorage storage) {
this.uriExtractor = uriExtractor; this.uriExtractor = uriExtractor;
this.cache = cache; this.storage = storage;
} }
/** /**
@ -82,15 +83,15 @@ class CacheInvalidator {
String theUri = uriExtractor.getURI(host, req); String theUri = uriExtractor.getURI(host, req);
HttpCacheEntry parent = cache.getEntry(theUri); HttpCacheEntry parent = storage.getEntry(theUri);
log.debug("parent entry: " + parent); log.debug("parent entry: " + parent);
if (parent != null) { if (parent != null) {
for (String variantURI : parent.getVariantURIs()) { for (String variantURI : parent.getVariantURIs()) {
cache.removeEntry(variantURI); storage.removeEntry(variantURI);
} }
cache.removeEntry(theUri); storage.removeEntry(theUri);
} }
URL reqURL; URL reqURL;
try { try {
@ -115,7 +116,7 @@ class CacheInvalidator {
protected void flushUriIfSameHost(URL requestURL, URL targetURL) throws IOException { protected void flushUriIfSameHost(URL requestURL, URL targetURL) throws IOException {
if (targetURL.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) { 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.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; 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.HeaderConstants;
import org.apache.http.client.cache.HttpCache; import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry; 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.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine; 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 @ThreadSafe // So long as the responseCache implementation is threadsafe
public class CachingHttpClient implements HttpClient { 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 static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
private final AtomicLong cacheHits = new AtomicLong(); private final AtomicLong cacheHits = new AtomicLong();
@ -79,19 +71,14 @@ public class CachingHttpClient implements HttpClient {
private final HttpClient backend; private final HttpClient backend;
private final HttpCache responseCache; private final HttpCache responseCache;
private final ResourceFactory resourceFactory;
private final CacheValidityPolicy validityPolicy; private final CacheValidityPolicy validityPolicy;
private final ResponseCachingPolicy responseCachingPolicy; private final ResponseCachingPolicy responseCachingPolicy;
private final URIExtractor uriExtractor;
private final CachedHttpResponseGenerator responseGenerator; private final CachedHttpResponseGenerator responseGenerator;
private final CacheInvalidator cacheInvalidator;
private final CacheableRequestPolicy cacheableRequestPolicy; private final CacheableRequestPolicy cacheableRequestPolicy;
private final CachedResponseSuitabilityChecker suitabilityChecker; private final CachedResponseSuitabilityChecker suitabilityChecker;
private final ConditionalRequestBuilder conditionalRequestBuilder; private final ConditionalRequestBuilder conditionalRequestBuilder;
private final CacheEntryUpdater cacheEntryUpdater;
private final int maxObjectSizeBytes; private final int maxObjectSizeBytes;
private final boolean sharedCache; private final boolean sharedCache;
@ -103,7 +90,6 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient( public CachingHttpClient(
HttpClient client, HttpClient client,
HttpCache cache, HttpCache cache,
ResourceFactory resourceFactory,
CacheConfig config) { CacheConfig config) {
super(); super();
if (client == null) { if (client == null) {
@ -112,9 +98,6 @@ public class CachingHttpClient implements HttpClient {
if (cache == null) { if (cache == null) {
throw new IllegalArgumentException("HttpCache may not be null"); throw new IllegalArgumentException("HttpCache may not be null");
} }
if (resourceFactory == null) {
throw new IllegalArgumentException("ResourceFactory may not be null");
}
if (config == null) { if (config == null) {
throw new IllegalArgumentException("CacheConfig may not be null"); throw new IllegalArgumentException("CacheConfig may not be null");
} }
@ -122,16 +105,12 @@ public class CachingHttpClient implements HttpClient {
this.sharedCache = config.isSharedCache(); this.sharedCache = config.isSharedCache();
this.backend = client; this.backend = client;
this.responseCache = cache; this.responseCache = cache;
this.resourceFactory = resourceFactory;
this.validityPolicy = new CacheValidityPolicy(); this.validityPolicy = new CacheValidityPolicy();
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache); this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache);
this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy); this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
this.uriExtractor = new URIExtractor();
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
this.cacheableRequestPolicy = new CacheableRequestPolicy(); this.cacheableRequestPolicy = new CacheableRequestPolicy();
this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy); this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy);
this.conditionalRequestBuilder = new ConditionalRequestBuilder(); this.conditionalRequestBuilder = new ConditionalRequestBuilder();
this.cacheEntryUpdater = new CacheEntryUpdater(this.resourceFactory);
this.responseCompliance = new ResponseProtocolCompliance(); this.responseCompliance = new ResponseProtocolCompliance();
this.requestCompliance = new RequestProtocolCompliance(); this.requestCompliance = new RequestProtocolCompliance();
@ -139,91 +118,73 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient() { public CachingHttpClient() {
this(new DefaultHttpClient(), this(new DefaultHttpClient(),
new BasicHttpCache(MAX_CACHE_ENTRIES), new BasicHttpCache(),
new HeapResourceFactory(),
new CacheConfig()); new CacheConfig());
} }
public CachingHttpClient(CacheConfig config) { public CachingHttpClient(CacheConfig config) {
this(new DefaultHttpClient(), this(new DefaultHttpClient(),
new BasicHttpCache(MAX_CACHE_ENTRIES), new BasicHttpCache(config),
new HeapResourceFactory(),
config); config);
} }
public CachingHttpClient(HttpClient client) { public CachingHttpClient(HttpClient client) {
this(client, this(client,
new BasicHttpCache(MAX_CACHE_ENTRIES), new BasicHttpCache(),
new HeapResourceFactory(),
new CacheConfig()); new CacheConfig());
} }
public CachingHttpClient(HttpClient client, CacheConfig config) { public CachingHttpClient(HttpClient client, CacheConfig config) {
this(client, this(client,
new BasicHttpCache(MAX_CACHE_ENTRIES), new BasicHttpCache(config),
new HeapResourceFactory(),
config); config);
} }
public CachingHttpClient( public CachingHttpClient(
HttpCache cache, HttpCache cache) {
ResourceFactory resourceFactory) {
this(new DefaultHttpClient(), this(new DefaultHttpClient(),
cache, cache,
resourceFactory,
new CacheConfig()); new CacheConfig());
} }
public CachingHttpClient( public CachingHttpClient(
HttpCache cache, HttpCache cache,
ResourceFactory resourceFactory,
CacheConfig config) { CacheConfig config) {
this(new DefaultHttpClient(), this(new DefaultHttpClient(),
cache, cache,
resourceFactory,
config); config);
} }
public CachingHttpClient( public CachingHttpClient(
HttpClient client, HttpClient client,
HttpCache cache, HttpCache cache) {
ResourceFactory resourceFactory) {
this(client, this(client,
cache, cache,
resourceFactory,
new CacheConfig()); new CacheConfig());
} }
CachingHttpClient( CachingHttpClient(
HttpClient backend, HttpClient backend,
ResourceFactory resourceFactory,
CacheValidityPolicy validityPolicy, CacheValidityPolicy validityPolicy,
ResponseCachingPolicy responseCachingPolicy, ResponseCachingPolicy responseCachingPolicy,
URIExtractor uriExtractor,
HttpCache responseCache, HttpCache responseCache,
CachedHttpResponseGenerator responseGenerator, CachedHttpResponseGenerator responseGenerator,
CacheInvalidator cacheInvalidator,
CacheableRequestPolicy cacheableRequestPolicy, CacheableRequestPolicy cacheableRequestPolicy,
CachedResponseSuitabilityChecker suitabilityChecker, CachedResponseSuitabilityChecker suitabilityChecker,
ConditionalRequestBuilder conditionalRequestBuilder, ConditionalRequestBuilder conditionalRequestBuilder,
CacheEntryUpdater entryUpdater,
ResponseProtocolCompliance responseCompliance, ResponseProtocolCompliance responseCompliance,
RequestProtocolCompliance requestCompliance) { RequestProtocolCompliance requestCompliance) {
CacheConfig config = new CacheConfig(); CacheConfig config = new CacheConfig();
this.maxObjectSizeBytes = config.getMaxObjectSizeBytes(); this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
this.sharedCache = config.isSharedCache(); this.sharedCache = config.isSharedCache();
this.backend = backend; this.backend = backend;
this.resourceFactory = resourceFactory;
this.validityPolicy = validityPolicy; this.validityPolicy = validityPolicy;
this.responseCachingPolicy = responseCachingPolicy; this.responseCachingPolicy = responseCachingPolicy;
this.uriExtractor = uriExtractor;
this.responseCache = responseCache; this.responseCache = responseCache;
this.responseGenerator = responseGenerator; this.responseGenerator = responseGenerator;
this.cacheInvalidator = cacheInvalidator;
this.cacheableRequestPolicy = cacheableRequestPolicy; this.cacheableRequestPolicy = cacheableRequestPolicy;
this.suitabilityChecker = suitabilityChecker; this.suitabilityChecker = suitabilityChecker;
this.conditionalRequestBuilder = conditionalRequestBuilder; this.conditionalRequestBuilder = conditionalRequestBuilder;
this.cacheEntryUpdater = entryUpdater;
this.responseCompliance = responseCompliance; this.responseCompliance = responseCompliance;
this.requestCompliance = requestCompliance; this.requestCompliance = requestCompliance;
} }
@ -409,13 +370,13 @@ public class CachingHttpClient implements HttpClient {
throw new ClientProtocolException(e); throw new ClientProtocolException(e);
} }
cacheInvalidator.flushInvalidatedCacheEntries(target, request); responseCache.flushInvalidatedCacheEntriesFor(target, request);
if (!cacheableRequestPolicy.isServableFromCache(request)) { if (!cacheableRequestPolicy.isServableFromCache(request)) {
return callBackend(target, request, context); return callBackend(target, request, context);
} }
HttpCacheEntry entry = getCacheEntry(target, request); HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
if (entry == null) { if (entry == null) {
cacheMisses.getAndIncrement(); cacheMisses.getAndIncrement();
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
@ -471,18 +432,6 @@ public class CachingHttpClient implements HttpClient {
return new Date(); 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) { boolean clientRequestsOurOptions(HttpRequest request) {
RequestLine line = request.getRequestLine(); RequestLine line = request.getRequestLine();
@ -533,101 +482,14 @@ public class CachingHttpClient implements HttpClient {
int statusCode = backendResponse.getStatusLine().getStatusCode(); int statusCode = backendResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) { if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
cacheUpdates.getAndIncrement(); cacheUpdates.getAndIncrement();
HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry( return responseCache.updateCacheEntry(target, request, cacheEntry,
request.getRequestLine().getUri(), backendResponse, requestDate, responseDate);
cacheEntry,
requestDate,
responseDate,
backendResponse);
storeInCache(target, request, updatedEntry);
return responseGenerator.generateResponse(updatedEntry);
} }
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate, return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
backendResponse); 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( HttpResponse handleBackendResponse(
HttpHost target, HttpHost target,
HttpRequest request, HttpRequest request,
@ -640,40 +502,13 @@ public class CachingHttpClient implements HttpClient {
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse); boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
HttpResponse corrected = backendResponse;
if (cacheable) { if (cacheable) {
return responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate,
SizeLimitedResponseReader responseReader = getResponseReader(request, backendResponse); responseDate);
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);
}
} }
String uri = uriExtractor.getURI(target, request); responseCache.flushCacheEntriesFor(target, request);
responseCache.removeEntry(uri); return backendResponse;
return corrected;
}
SizeLimitedResponseReader getResponseReader(HttpRequest request, HttpResponse backEndResponse) {
return new SizeLimitedResponseReader(
resourceFactory, maxObjectSizeBytes, request, backEndResponse);
} }
} }

View File

@ -33,13 +33,13 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.apache.http.annotation.ThreadSafe; 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.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback; import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.Resource; 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} * 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, * 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()} * 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()} * 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. * 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. * and similar.
* *
* @since 4.1 * @since 4.1
*/ */
@ThreadSafe @ThreadSafe
public class ManagedHttpCache implements HttpCache { public class ManagedHttpCacheStorage implements HttpCacheStorage {
private final CacheMap entries; private final CacheMap entries;
private final ReferenceQueue<HttpCacheEntry> morque; private final ReferenceQueue<HttpCacheEntry> morque;
@ -61,9 +61,9 @@ public class ManagedHttpCache implements HttpCache {
private volatile boolean shutdown; private volatile boolean shutdown;
public ManagedHttpCache(int maxEntries) { public ManagedHttpCacheStorage(final CacheConfig config) {
super(); super();
this.entries = new CacheMap(maxEntries); this.entries = new CacheMap(config.getMaxCacheEntries());
this.morque = new ReferenceQueue<HttpCacheEntry>(); this.morque = new ReferenceQueue<HttpCacheEntry>();
this.resources = new HashSet<ResourceReference>(); this.resources = new HashSet<ResourceReference>();
} }

View File

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

View File

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

View File

@ -48,12 +48,13 @@ public abstract class AbstractProtocolTest {
originResponse = make200Response(); 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); mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class); mockCache = EasyMock.createMock(HttpCache.class);
params = new CacheConfig(); impl = new CachingHttpClient(mockBackend, cache, params);
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
} }
protected void replayMocks() { protected void replayMocks() {
@ -89,18 +90,23 @@ public abstract class AbstractProtocolTest {
mockBackend = EasyMock.createMock(HttpClient.class); mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.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) EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
.anyTimes(); .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(); EasyMock.expectLastCall().anyTimes();
} }
protected void behaveAsNonSharedCache() { protected void behaveAsNonSharedCache() {
params.setSharedCache(false); params.setSharedCache(false);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params); impl = new CachingHttpClient(mockBackend, cache, params);
} }
public AbstractProtocolTest() { public AbstractProtocolTest() {

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.client.cache; package org.apache.http.impl.client.cache;
import java.util.Date; import java.util.Date;
import java.util.Set;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.client.cache.HttpCacheEntry; 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); 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); request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
originResponse = make200Response(); 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); mockBackend = EasyMock.createMock(HttpClient.class);
mockEntity = EasyMock.createMock(HttpEntity.class); mockEntity = EasyMock.createMock(HttpEntity.class);
mockCache = EasyMock.createMock(HttpCache.class); mockCache = EasyMock.createMock(HttpCache.class);
CacheConfig params = new CacheConfig(); impl = new CachingHttpClient(mockBackend, cache, params);
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
} }
private HttpResponse make200Response() { private HttpResponse make200Response() {

View File

@ -195,9 +195,13 @@ public class HttpTestUtils {
*/ */
public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2) public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2)
throws Exception { throws Exception {
return (equivalent(r1.getEntity(), r2.getEntity()) final boolean entitiesEquivalent = equivalent(r1.getEntity(), r2.getEntity());
&& semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine()) && isEndToEndHeaderSubset( if (!entitiesEquivalent) return false;
r1, r2)); 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. */ /* 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.HttpHost;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.ProtocolVersion; 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.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.BasicHttpRequest;
import org.easymock.classextension.EasyMock; 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 static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
private CacheInvalidator impl; private CacheInvalidator impl;
private HttpCache mockCache; private HttpCacheStorage mockStorage;
private HttpHost host; private HttpHost host;
private URIExtractor extractor; private URIExtractor extractor;
private CacheEntry mockEntry; private CacheEntry mockEntry;
@ -54,20 +54,20 @@ public class TestCacheInvalidator {
@Before @Before
public void setUp() { public void setUp() {
host = new HttpHost("foo.example.com"); host = new HttpHost("foo.example.com");
mockCache = EasyMock.createMock(HttpCache.class); mockStorage = EasyMock.createMock(HttpCacheStorage.class);
extractor = new URIExtractor(); extractor = new URIExtractor();
mockEntry = EasyMock.createMock(CacheEntry.class); mockEntry = EasyMock.createMock(CacheEntry.class);
impl = new CacheInvalidator(extractor, mockCache); impl = new CacheInvalidator(extractor, mockStorage);
} }
private void replayMocks() { private void replayMocks() {
EasyMock.replay(mockCache); EasyMock.replay(mockStorage);
EasyMock.replay(mockEntry); EasyMock.replay(mockEntry);
} }
private void verifyMocks() { private void verifyMocks() {
EasyMock.verify(mockCache); EasyMock.verify(mockStorage);
EasyMock.verify(mockEntry); EasyMock.verify(mockEntry);
} }
@ -283,16 +283,16 @@ public class TestCacheInvalidator {
} }
private void cacheReturnsEntryForUri(String theUri) throws IOException { 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 { 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")); new IOException("TOTAL FAIL"));
} }
private void entryIsRemoved(String theUri) throws IOException { 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; package org.apache.http.impl.client.cache;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; 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.ResponseHandler;
import org.apache.http.client.cache.HttpCache; import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry; 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.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams; import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext; 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 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 CachingHttpClient impl;
private boolean mockedImpl; private boolean mockedImpl;
private ResourceFactory mockResourceFactory;
private CacheInvalidator mockInvalidator;
private CacheValidityPolicy mockValidityPolicy; private CacheValidityPolicy mockValidityPolicy;
private CacheableRequestPolicy mockRequestPolicy; private CacheableRequestPolicy mockRequestPolicy;
private HttpClient mockBackend; private HttpClient mockBackend;
@ -92,20 +77,14 @@ public class TestCachingHttpClient {
private ResponseCachingPolicy mockResponsePolicy; private ResponseCachingPolicy mockResponsePolicy;
private HttpResponse mockBackendResponse; private HttpResponse mockBackendResponse;
private CacheEntry mockCacheEntry; private CacheEntry mockCacheEntry;
private CacheEntry mockVariantCacheEntry;
private CacheEntry mockUpdatedCacheEntry;
private URIExtractor mockExtractor;
private CachedHttpResponseGenerator mockResponseGenerator; private CachedHttpResponseGenerator mockResponseGenerator;
private SizeLimitedResponseReader mockResponseReader;
private ClientConnectionManager mockConnectionManager; private ClientConnectionManager mockConnectionManager;
private ResponseHandler<Object> mockHandler; private ResponseHandler<Object> mockHandler;
private HttpUriRequest mockUriRequest; private HttpUriRequest mockUriRequest;
private HttpResponse mockCachedResponse; private HttpResponse mockCachedResponse;
private HttpResponse mockReconstructedResponse;
private ConditionalRequestBuilder mockConditionalRequestBuilder; private ConditionalRequestBuilder mockConditionalRequestBuilder;
private HttpRequest mockConditionalRequest; private HttpRequest mockConditionalRequest;
private StatusLine mockStatusLine; private StatusLine mockStatusLine;
private CacheEntryUpdater mockCacheEntryUpdater;
private ResponseProtocolCompliance mockResponseProtocolCompliance; private ResponseProtocolCompliance mockResponseProtocolCompliance;
private RequestProtocolCompliance mockRequestProtocolCompliance; private RequestProtocolCompliance mockRequestProtocolCompliance;
@ -119,8 +98,6 @@ public class TestCachingHttpClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Before @Before
public void setUp() { public void setUp() {
mockResourceFactory = EasyMock.createMock(ResourceFactory.class);
mockInvalidator = EasyMock.createMock(CacheInvalidator.class);
mockRequestPolicy = EasyMock.createMock(CacheableRequestPolicy.class); mockRequestPolicy = EasyMock.createMock(CacheableRequestPolicy.class);
mockValidityPolicy = EasyMock.createMock(CacheValidityPolicy.class); mockValidityPolicy = EasyMock.createMock(CacheValidityPolicy.class);
mockBackend = EasyMock.createMock(HttpClient.class); mockBackend = EasyMock.createMock(HttpClient.class);
@ -132,17 +109,11 @@ public class TestCachingHttpClient {
mockBackendResponse = EasyMock.createMock(HttpResponse.class); mockBackendResponse = EasyMock.createMock(HttpResponse.class);
mockUriRequest = EasyMock.createMock(HttpUriRequest.class); mockUriRequest = EasyMock.createMock(HttpUriRequest.class);
mockCacheEntry = EasyMock.createMock(CacheEntry.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); mockResponseGenerator = EasyMock.createMock(CachedHttpResponseGenerator.class);
mockCachedResponse = EasyMock.createMock(HttpResponse.class); mockCachedResponse = EasyMock.createMock(HttpResponse.class);
mockConditionalRequestBuilder = EasyMock.createMock(ConditionalRequestBuilder.class); mockConditionalRequestBuilder = EasyMock.createMock(ConditionalRequestBuilder.class);
mockConditionalRequest = EasyMock.createMock(HttpRequest.class); mockConditionalRequest = EasyMock.createMock(HttpRequest.class);
mockStatusLine = EasyMock.createMock(StatusLine.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); mockResponseProtocolCompliance = EasyMock.createMock(ResponseProtocolCompliance.class);
mockRequestProtocolCompliance = EasyMock.createMock(RequestProtocolCompliance.class); mockRequestProtocolCompliance = EasyMock.createMock(RequestProtocolCompliance.class);
@ -154,32 +125,24 @@ public class TestCachingHttpClient {
params = new BasicHttpParams(); params = new BasicHttpParams();
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance); mockRequestProtocolCompliance);
} }
private void replayMocks() { private void replayMocks() {
EasyMock.replay(mockResourceFactory);
EasyMock.replay(mockInvalidator);
EasyMock.replay(mockRequestPolicy); EasyMock.replay(mockRequestPolicy);
EasyMock.replay(mockValidityPolicy); EasyMock.replay(mockValidityPolicy);
EasyMock.replay(mockSuitabilityChecker); EasyMock.replay(mockSuitabilityChecker);
EasyMock.replay(mockResponsePolicy); EasyMock.replay(mockResponsePolicy);
EasyMock.replay(mockCacheEntry); EasyMock.replay(mockCacheEntry);
EasyMock.replay(mockVariantCacheEntry);
EasyMock.replay(mockResponseGenerator); EasyMock.replay(mockResponseGenerator);
EasyMock.replay(mockExtractor);
EasyMock.replay(mockBackend); EasyMock.replay(mockBackend);
EasyMock.replay(mockCache); EasyMock.replay(mockCache);
EasyMock.replay(mockConnectionManager); EasyMock.replay(mockConnectionManager);
@ -190,9 +153,6 @@ public class TestCachingHttpClient {
EasyMock.replay(mockConditionalRequestBuilder); EasyMock.replay(mockConditionalRequestBuilder);
EasyMock.replay(mockConditionalRequest); EasyMock.replay(mockConditionalRequest);
EasyMock.replay(mockStatusLine); EasyMock.replay(mockStatusLine);
EasyMock.replay(mockCacheEntryUpdater);
EasyMock.replay(mockResponseReader);
EasyMock.replay(mockReconstructedResponse);
EasyMock.replay(mockResponseProtocolCompliance); EasyMock.replay(mockResponseProtocolCompliance);
EasyMock.replay(mockRequestProtocolCompliance); EasyMock.replay(mockRequestProtocolCompliance);
if (mockedImpl) { if (mockedImpl) {
@ -201,16 +161,12 @@ public class TestCachingHttpClient {
} }
private void verifyMocks() { private void verifyMocks() {
EasyMock.verify(mockResourceFactory);
EasyMock.verify(mockInvalidator);
EasyMock.verify(mockRequestPolicy); EasyMock.verify(mockRequestPolicy);
EasyMock.verify(mockValidityPolicy); EasyMock.verify(mockValidityPolicy);
EasyMock.verify(mockSuitabilityChecker); EasyMock.verify(mockSuitabilityChecker);
EasyMock.verify(mockResponsePolicy); EasyMock.verify(mockResponsePolicy);
EasyMock.verify(mockCacheEntry); EasyMock.verify(mockCacheEntry);
EasyMock.verify(mockVariantCacheEntry);
EasyMock.verify(mockResponseGenerator); EasyMock.verify(mockResponseGenerator);
EasyMock.verify(mockExtractor);
EasyMock.verify(mockBackend); EasyMock.verify(mockBackend);
EasyMock.verify(mockCache); EasyMock.verify(mockCache);
EasyMock.verify(mockConnectionManager); EasyMock.verify(mockConnectionManager);
@ -221,9 +177,6 @@ public class TestCachingHttpClient {
EasyMock.verify(mockConditionalRequestBuilder); EasyMock.verify(mockConditionalRequestBuilder);
EasyMock.verify(mockConditionalRequest); EasyMock.verify(mockConditionalRequest);
EasyMock.verify(mockStatusLine); EasyMock.verify(mockStatusLine);
EasyMock.verify(mockCacheEntryUpdater);
EasyMock.verify(mockResponseReader);
EasyMock.verify(mockReconstructedResponse);
EasyMock.verify(mockResponseProtocolCompliance); EasyMock.verify(mockResponseProtocolCompliance);
EasyMock.verify(mockRequestProtocolCompliance); EasyMock.verify(mockRequestProtocolCompliance);
if (mockedImpl) { if (mockedImpl) {
@ -233,20 +186,12 @@ public class TestCachingHttpClient {
@Test @Test
public void testCacheableResponsesGoIntoCache() throws Exception { public void testCacheableResponsesGoIntoCache() throws Exception {
mockImplMethods(STORE_IN_CACHE, GET_RESPONSE_READER);
responsePolicyAllowsCaching(true); responsePolicyAllowsCaching(true);
responseProtocolValidationIsCalled(); responseProtocolValidationIsCalled();
getMockResponseReader(); EasyMock.expect(mockCache.cacheAndReturnResponse(host, request, mockBackendResponse, requestDate, responseDate))
responseRead(); .andReturn(mockCachedResponse);
responseLimitReached(false);
responseGetResource();
storeInCacheWasCalled();
responseIsGeneratedFromCache();
responseStatusLineIsInspectable();
responseGetHeaders();
responseDoesNotHaveExplicitContentLength();
replayMocks(); replayMocks();
HttpResponse result = impl.handleBackendResponse(host, request, requestDate, HttpResponse result = impl.handleBackendResponse(host, request, requestDate,
@ -283,37 +228,13 @@ public class TestCachingHttpClient {
errors); 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 @Test
public void testCacheMissCausesBackendRequest() throws Exception { public void testCacheMissCausesBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND); mockImplMethods(CALL_BACKEND);
cacheInvalidatorWasCalled(); cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
getCacheEntryReturns(null); getCacheEntryReturns(null);
requestProtocolValidationIsCalled(); requestProtocolValidationIsCalled();
requestIsFatallyNonCompliant(null); requestIsFatallyNonCompliant(null);
@ -331,7 +252,7 @@ public class TestCachingHttpClient {
@Test @Test
public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception { public void testUnsuitableUnvalidatableCacheEntryCausesBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, CALL_BACKEND); mockImplMethods(CALL_BACKEND);
cacheInvalidatorWasCalled(); cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled(); requestProtocolValidationIsCalled();
@ -354,7 +275,7 @@ public class TestCachingHttpClient {
@Test @Test
public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception { public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
mockImplMethods(GET_CACHE_ENTRY, REVALIDATE_CACHE_ENTRY); mockImplMethods(REVALIDATE_CACHE_ENTRY);
cacheInvalidatorWasCalled(); cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled(); requestProtocolValidationIsCalled();
@ -384,11 +305,9 @@ public class TestCachingHttpClient {
backendCallWasMadeWithRequest(mockConditionalRequest); backendCallWasMadeWithRequest(mockConditionalRequest);
getCurrentDateReturns(responseDate); getCurrentDateReturns(responseDate);
backendResponseCodeIs(HttpStatus.SC_OK); backendResponseCodeIs(HttpStatus.SC_OK);
cacheEntryUpdaterCalled(); EasyMock.expect(mockCache.updateCacheEntry(host, request,
cacheEntryHasVariants(false, mockUpdatedCacheEntry); mockCacheEntry, mockBackendResponse, requestDate, responseDate))
extractTheURI("http://foo.example.com"); .andReturn(mockCachedResponse);
putInCache("http://foo.example.com", mockUpdatedCacheEntry);
responseIsGeneratedFromCache(mockUpdatedCacheEntry);
replayMocks(); replayMocks();
@ -406,17 +325,15 @@ public class TestCachingHttpClient {
@Test @Test
public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse() public void testRevalidationUpdatesCacheEntryAndPutsItToCacheWhen304ReturningCachedResponse()
throws Exception { throws Exception {
mockImplMethods(GET_CURRENT_DATE, STORE_IN_CACHE); mockImplMethods(GET_CURRENT_DATE);
conditionalRequestBuilderCalled(); conditionalRequestBuilderCalled();
getCurrentDateReturns(requestDate); getCurrentDateReturns(requestDate);
backendCallWasMadeWithRequest(mockConditionalRequest); backendCallWasMadeWithRequest(mockConditionalRequest);
getCurrentDateReturns(responseDate); getCurrentDateReturns(responseDate);
backendResponseCodeIs(HttpStatus.SC_NOT_MODIFIED); backendResponseCodeIs(HttpStatus.SC_NOT_MODIFIED);
EasyMock.expect(mockCache.updateCacheEntry(host, request,
cacheEntryUpdaterCalled(); mockCacheEntry, mockBackendResponse, requestDate, responseDate))
storeInCacheWasCalled(mockUpdatedCacheEntry); .andReturn(mockCachedResponse);
responseIsGeneratedFromCache(mockUpdatedCacheEntry);
replayMocks(); replayMocks();
@ -432,7 +349,6 @@ public class TestCachingHttpClient {
@Test @Test
public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception { public void testSuitableCacheEntryDoesNotCauseBackendRequest() throws Exception {
mockImplMethods(GET_CACHE_ENTRY);
cacheInvalidatorWasCalled(); cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
requestProtocolValidationIsCalled(); requestProtocolValidationIsCalled();
@ -465,13 +381,11 @@ public class TestCachingHttpClient {
@Test @Test
public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception { public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
final String theURI = "theURI";
Date currentDate = new Date(); Date currentDate = new Date();
responsePolicyAllowsCaching(false); responsePolicyAllowsCaching(false);
responseProtocolValidationIsCalled(); responseProtocolValidationIsCalled();
extractTheURI(theURI); flushCache();
removeFromCache(theURI);
replayMocks(); replayMocks();
HttpResponse result = impl.handleBackendResponse(host, request, currentDate, HttpResponse result = impl.handleBackendResponse(host, request, currentDate,
@ -481,115 +395,6 @@ public class TestCachingHttpClient {
Assert.assertSame(mockBackendResponse, result); 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 @Test
public void testCallsSelfForExecuteOnHostRequestWithNullContext() throws Exception { public void testCallsSelfForExecuteOnHostRequestWithNullContext() throws Exception {
@ -599,17 +404,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse; final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -641,17 +442,13 @@ public class TestCachingHttpClient {
final Object value = new Object(); final Object value = new Object();
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -691,17 +488,13 @@ public class TestCachingHttpClient {
final HttpContext theContext = context; final HttpContext theContext = context;
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -733,17 +526,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse; final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -773,17 +562,13 @@ public class TestCachingHttpClient {
final HttpResponse theResponse = mockBackendResponse; final HttpResponse theResponse = mockBackendResponse;
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -816,17 +601,13 @@ public class TestCachingHttpClient {
final Object theValue = new Object(); final Object theValue = new Object();
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -861,17 +642,13 @@ public class TestCachingHttpClient {
final Object theValue = new Object(); final Object theValue = new Object();
impl = new CachingHttpClient( impl = new CachingHttpClient(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance) { mockRequestProtocolCompliance) {
@Override @Override
@ -916,17 +693,13 @@ public class TestCachingHttpClient {
@Test @Test
public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception { public void testResponseIsGeneratedWhenCacheEntryIsUsable() throws Exception {
final String theURI = "http://foo";
requestIsFatallyNonCompliant(null); requestIsFatallyNonCompliant(null);
requestProtocolValidationIsCalled(); requestProtocolValidationIsCalled();
cacheInvalidatorWasCalled(); cacheInvalidatorWasCalled();
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
cacheEntrySuitable(true); cacheEntrySuitable(true);
extractTheURI(theURI); getCacheEntryReturns(mockCacheEntry);
gotCacheHit(theURI);
responseIsGeneratedFromCache(); responseIsGeneratedFromCache();
cacheEntryHasVariants(false);
replayMocks(); replayMocks();
impl.execute(host, request, context); impl.execute(host, request, context);
@ -953,123 +726,17 @@ public class TestCachingHttpClient {
Assert.assertTrue(gotException); 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 @Test
public void testIsSharedCache() { public void testIsSharedCache() {
Assert.assertTrue(impl.isSharedCache()); Assert.assertTrue(impl.isSharedCache());
} }
private void getCacheEntryReturns(HttpCacheEntry result) throws IOException {
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(result);
}
private void cacheInvalidatorWasCalled() throws IOException { private void cacheInvalidatorWasCalled() throws IOException {
mockInvalidator.flushInvalidatedCacheEntries( mockCache.flushInvalidatedCacheEntriesFor(
EasyMock.<HttpHost>anyObject(), EasyMock.<HttpHost>anyObject(),
EasyMock.<HttpRequest>anyObject()); EasyMock.<HttpRequest>anyObject());
} }
@ -1096,22 +763,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpCacheEntry>anyObject())).andReturn(b); 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) { private void backendResponseCodeIs(int code) {
EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(mockStatusLine); EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(mockStatusLine);
EasyMock.expect(mockStatusLine.getStatusCode()).andReturn(code); EasyMock.expect(mockStatusLine.getStatusCode()).andReturn(code);
@ -1128,42 +779,11 @@ public class TestCachingHttpClient {
EasyMock.expect(impl.getCurrentDate()).andReturn(date); 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) { private void requestPolicyAllowsCaching(boolean allow) {
EasyMock.expect(mockRequestPolicy.isServableFromCache( EasyMock.expect(mockRequestPolicy.isServableFromCache(
EasyMock.<HttpRequest>anyObject())).andReturn(allow); 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 { private void backendCallWasMadeWithRequest(HttpRequest request) throws IOException {
EasyMock.expect(mockBackend.execute( EasyMock.expect(mockBackend.execute(
EasyMock.<HttpHost>anyObject(), EasyMock.<HttpHost>anyObject(),
@ -1178,10 +798,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpResponse>anyObject())).andReturn(allow); EasyMock.<HttpResponse>anyObject())).andReturn(allow);
} }
private void gotCacheMiss(String theURI) throws Exception {
EasyMock.expect(mockCache.getEntry(theURI)).andReturn(null);
}
private void cacheEntrySuitable(boolean suitable) { private void cacheEntrySuitable(boolean suitable) {
EasyMock.expect( EasyMock.expect(
mockSuitabilityChecker.canCachedResponseBeUsed( mockSuitabilityChecker.canCachedResponseBeUsed(
@ -1190,68 +806,13 @@ public class TestCachingHttpClient {
EasyMock.<HttpCacheEntry>anyObject())).andReturn(suitable); 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() { private void responseIsGeneratedFromCache() {
EasyMock.expect(mockResponseGenerator.generateResponse( EasyMock.expect(mockResponseGenerator.generateResponse(
EasyMock.<HttpCacheEntry>anyObject())).andReturn(mockCachedResponse); EasyMock.<HttpCacheEntry>anyObject())).andReturn(mockCachedResponse);
} }
private void responseStatusLineIsInspectable() { private void flushCache() throws IOException {
EasyMock.expect(mockBackendResponse.getStatusLine()).andReturn(new OKStatus()).anyTimes(); mockCache.flushCacheEntriesFor(host, request);
}
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 handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response) private void handleBackendResponseReturnsResponse(HttpRequest request, HttpResponse response)
@ -1265,20 +826,6 @@ public class TestCachingHttpClient {
EasyMock.<HttpResponse>anyObject())).andReturn(response); 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 { private void responseProtocolValidationIsCalled() throws ClientProtocolException {
mockResponseProtocolCompliance.ensureProtocolCompliance( mockResponseProtocolCompliance.ensureProtocolCompliance(
EasyMock.<HttpRequest>anyObject(), EasyMock.<HttpRequest>anyObject(),
@ -1301,17 +848,13 @@ public class TestCachingHttpClient {
mockedImpl = true; mockedImpl = true;
impl = EasyMock.createMockBuilder(CachingHttpClient.class).withConstructor( impl = EasyMock.createMockBuilder(CachingHttpClient.class).withConstructor(
mockBackend, mockBackend,
mockResourceFactory,
mockValidityPolicy, mockValidityPolicy,
mockResponsePolicy, mockResponsePolicy,
mockExtractor,
mockCache, mockCache,
mockResponseGenerator, mockResponseGenerator,
mockInvalidator,
mockRequestPolicy, mockRequestPolicy,
mockSuitabilityChecker, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockConditionalRequestBuilder,
mockCacheEntryUpdater,
mockResponseProtocolCompliance, mockResponseProtocolCompliance,
mockRequestProtocolCompliance).addMockedMethods(methods).createMock(); mockRequestProtocolCompliance).addMockedMethods(methods).createMock();
} }

View File

@ -93,14 +93,16 @@ public class TestProtocolDeviations {
originResponse = make200Response(); 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); mockBackend = EasyMock.createMock(HttpClient.class);
mockEntity = EasyMock.createMock(HttpEntity.class); mockEntity = EasyMock.createMock(HttpEntity.class);
mockCache = EasyMock.createMock(HttpCache.class); mockCache = EasyMock.createMock(HttpCache.class);
CacheConfig params = new CacheConfig(); impl = new CachingHttpClient(mockBackend, cache, params);
params.setMaxObjectSizeBytes(MAX_BYTES);
impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
} }
private HttpResponse make200Response() { private HttpResponse make200Response() {

View File

@ -42,7 +42,6 @@ import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion; import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion; import org.apache.http.ProtocolVersion;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.entity.BasicHttpEntity; import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.RequestWrapper; import org.apache.http.impl.client.RequestWrapper;
@ -2220,9 +2219,9 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); 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); HttpRequest validate = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
validate.setHeader("If-None-Match", "\"etag\""); validate.setHeader("If-None-Match", "\"etag\"");
@ -2232,17 +2231,23 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
notModified.setHeader("Date", DateUtils.formatDate(now)); notModified.setHeader("Date", DateUtils.formatDate(now));
notModified.setHeader("ETag", "\"etag\""); 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( EasyMock.expect(
mockBackend.execute(EasyMock.eq(host), eqRequest(validate), (HttpContext) EasyMock mockBackend.execute(EasyMock.eq(host), eqRequest(validate), (HttpContext) EasyMock
.isNull())).andReturn(notModified); .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(); replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request); HttpResponse result = impl.execute(host, request);
verifyMocks(); verifyMocks();
Assert.assertEquals(200, result.getStatusLine().getStatusCode()); Assert.assertSame(reconstructed, result);
} }
@Test @Test
@ -2264,12 +2269,13 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); 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(); replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request); HttpResponse result = impl.execute(host, request);
verifyMocks(); verifyMocks();
@ -2305,16 +2311,17 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); 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( EasyMock.expect(
mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class), mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class),
(HttpContext) EasyMock.isNull())).andThrow( (HttpContext) EasyMock.isNull())).andThrow(
new IOException("can't talk to origin!")).anyTimes(); new IOException("can't talk to origin!")).anyTimes();
replayMocks(); replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request); HttpResponse result = impl.execute(host, request);
@ -2506,12 +2513,13 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes); 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(); replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request); HttpResponse result = impl.execute(host, request);
verifyMocks(); verifyMocks();
@ -2550,7 +2558,9 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
CacheEntry entry = new CacheEntry(requestTime, responseTime, hdrs, bytes); 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(); HttpResponse validated = make200Response();
validated.setHeader("Cache-Control", "public"); validated.setHeader("Cache-Control", "public");
@ -2558,18 +2568,20 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
validated.setHeader("Content-Length", "128"); validated.setHeader("Content-Length", "128");
validated.setEntity(new ByteArrayEntity(bytes)); validated.setEntity(new ByteArrayEntity(bytes));
HttpResponse reconstructed = make200Response();
Capture<HttpRequest> cap = new Capture<HttpRequest>(); Capture<HttpRequest> cap = new Capture<HttpRequest>();
EasyMock.expect(mockCache.getEntry("http://foo.example.com/thing")).andReturn(entry); mockCache.flushInvalidatedCacheEntriesFor(host, request);
EasyMock.expect(mockCache.getCacheEntry(host, request)).andReturn(entry);
mockCache.putEntry(EasyMock.isA(String.class), EasyMock.isA(HttpCacheEntry.class));
EasyMock.expect( EasyMock.expect(
mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.capture(cap), mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.capture(cap),
(HttpContext) EasyMock.isNull())).andReturn(validated).times(0, 1); (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(); replayMocks();
request = new BasicHttpRequest("GET", "/thing", HttpVersion.HTTP_1_1);
HttpResponse result = impl.execute(host, request); HttpResponse result = impl.execute(host, request);
verifyMocks(); 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; 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.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion; 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.apache.http.util.EntityUtils;
import org.easymock.classextension.EasyMock;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -46,56 +42,44 @@ public class TestSizeLimitedResponseReader {
private static final long MAX_SIZE = 4; private static final long MAX_SIZE = 4;
private HttpRequest request;
private SizeLimitedResponseReader impl; private SizeLimitedResponseReader impl;
private HttpRequest mockRequest;
private HttpResponse mockResponse;
private HttpEntity mockEntity;
private boolean mockedImpl;
@Before @Before
public void setUp() { public void setUp() {
mockRequest = EasyMock.createMock(HttpRequest.class); request = new HttpGet("http://foo.example.com/bar");
mockResponse = EasyMock.createMock(HttpResponse.class);
mockEntity = EasyMock.createMock(HttpEntity.class);
} }
@Test @Test
public void testLargeResponseIsTooLarge() throws Exception { public void testLargeResponseIsTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3, 4, 5}; byte[] buf = new byte[] { 1, 2, 3, 4, 5 };
requestReturnsRequestLine(); HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
responseReturnsProtocolVersion(); response.setEntity(new ByteArrayEntity(buf));
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf)); impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
initReader();
replayMocks();
impl.readResponse(); impl.readResponse();
boolean tooLarge = impl.isLimitReached(); boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse(); HttpResponse result = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity()); byte[] body = EntityUtils.toByteArray(result.getEntity());
verifyMocks();
Assert.assertTrue(tooLarge); Assert.assertTrue(tooLarge);
Assert.assertArrayEquals(buf, result); Assert.assertArrayEquals(buf, body);
} }
@Test @Test
public void testExactSizeResponseIsNotTooLarge() throws Exception { public void testExactSizeResponseIsNotTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3, 4 }; byte[] buf = new byte[] { 1, 2, 3, 4 };
requestReturnsRequestLine(); HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
responseReturnsProtocolVersion(); response.setEntity(new ByteArrayEntity(buf));
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf)); impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
initReader();
replayMocks();
impl.readResponse(); impl.readResponse();
boolean tooLarge = impl.isLimitReached(); boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse(); HttpResponse reconstructed = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity()); byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
verifyMocks();
Assert.assertFalse(tooLarge); Assert.assertFalse(tooLarge);
Assert.assertArrayEquals(buf, result); Assert.assertArrayEquals(buf, result);
} }
@ -103,18 +87,15 @@ public class TestSizeLimitedResponseReader {
@Test @Test
public void testSmallResponseIsNotTooLarge() throws Exception { public void testSmallResponseIsNotTooLarge() throws Exception {
byte[] buf = new byte[] { 1, 2, 3 }; byte[] buf = new byte[] { 1, 2, 3 };
requestReturnsRequestLine(); HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
responseReturnsProtocolVersion(); response.setEntity(new ByteArrayEntity(buf));
responseReturnsHeaders();
responseReturnsContent(new ByteArrayInputStream(buf)); impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
initReader();
replayMocks();
impl.readResponse(); impl.readResponse();
boolean tooLarge = impl.isLimitReached(); boolean tooLarge = impl.isLimitReached();
HttpResponse response = impl.getReconstructedResponse(); HttpResponse reconstructed = impl.getReconstructedResponse();
byte[] result = EntityUtils.toByteArray(response.getEntity()); byte[] result = EntityUtils.toByteArray(reconstructed.getEntity());
verifyMocks();
Assert.assertFalse(tooLarge); Assert.assertFalse(tooLarge);
Assert.assertArrayEquals(buf, result); Assert.assertArrayEquals(buf, result);
@ -122,56 +103,14 @@ public class TestSizeLimitedResponseReader {
@Test @Test
public void testResponseWithNoEntityIsNotTooLarge() throws Exception { 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(); impl.readResponse();
boolean tooLarge = impl.isLimitReached(); boolean tooLarge = impl.isLimitReached();
verifyMocks();
Assert.assertFalse(tooLarge); 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 { public class TestEhcacheHttpCache extends TestCase {
private Ehcache mockCache; private Ehcache mockCache;
private EhcacheHttpCache impl; private EhcacheHttpCacheStorage impl;
public void setUp() { public void setUp() {
mockCache = EasyMock.createMock(Ehcache.class); mockCache = EasyMock.createMock(Ehcache.class);
impl = new EhcacheHttpCache(mockCache); impl = new EhcacheHttpCacheStorage(mockCache);
} }
@Test @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();
}
}