diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCache.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCache.java
index 7eb696b37..67ac0827e 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCache.java
@@ -27,19 +27,34 @@
package org.apache.http.client.cache;
import java.io.IOException;
+import java.util.Date;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
/**
* @since 4.1
*/
public interface HttpCache {
- void putEntry(String key, HttpCacheEntry entry) throws IOException;
+ void flushCacheEntriesFor(HttpHost host, HttpRequest request)
+ throws IOException;
- HttpCacheEntry getEntry(String key) throws IOException;
+ void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request)
+ throws IOException;
- void removeEntry(String key) throws IOException;
+ HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request)
+ throws IOException;
- void updateEntry(
- String key, HttpCacheUpdateCallback callback) throws IOException;
+ HttpResponse cacheAndReturnResponse(
+ HttpHost host, HttpRequest request, HttpResponse originResponse,
+ Date requestSent, Date responseReceived)
+ throws IOException;
+
+ HttpResponse updateCacheEntry(
+ HttpHost target, HttpRequest request, HttpCacheEntry stale, HttpResponse originResponse,
+ Date requestSent, Date responseReceived)
+ throws IOException;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java
new file mode 100644
index 000000000..bde082a9c
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java
@@ -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
+ * .
+ *
+ */
+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;
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
index e31e74306..ea91ce0ba 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
@@ -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
- * .
- *
- */
package org.apache.http.impl.client.cache;
import java.io.IOException;
-import java.util.LinkedHashMap;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
-import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
+import org.apache.http.client.cache.Resource;
+import org.apache.http.client.cache.ResourceFactory;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.message.BasicHttpResponse;
-/**
- * Basic {@link HttpCache} implementation backed by an instance of {@link LinkedHashMap}.
- * This cache does NOT deallocate resources associated with the cache entries. It is intended
- * for use with {@link MemCacheEntry} and similar.
- *
- * @since 4.1
- */
-@ThreadSafe
public class BasicHttpCache implements HttpCache {
- private final CacheMap entries;
+ private final URIExtractor uriExtractor;
+ private final ResourceFactory resourceFactory;
+ private final int maxObjectSizeBytes;
+ private final CacheEntryUpdater cacheEntryUpdater;
+ private final CachedHttpResponseGenerator responseGenerator;
+ private final CacheInvalidator cacheInvalidator;
+ private final HttpCacheStorage storage;
- public BasicHttpCache(int maxEntries) {
- this.entries = new CacheMap(maxEntries);
+ public BasicHttpCache(ResourceFactory resourceFactory, HttpCacheStorage storage, CacheConfig config) {
+ this.resourceFactory = resourceFactory;
+ this.uriExtractor = new URIExtractor();
+ this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory);
+ this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
+ this.responseGenerator = new CachedHttpResponseGenerator();
+ this.storage = storage;
+ this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.storage);
}
- /**
- * Places a HttpCacheEntry in the cache
- *
- * @param url
- * Url to use as the cache key
- * @param entry
- * HttpCacheEntry to place in the cache
- */
- public synchronized void putEntry(String url, HttpCacheEntry entry) throws IOException {
- entries.put(url, entry);
+ public BasicHttpCache(CacheConfig config) {
+ this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config);
}
- /**
- * Gets an entry from the cache, if it exists
- *
- * @param url
- * Url that is the cache key
- * @return HttpCacheEntry if one exists, or null for cache miss
- */
- public synchronized HttpCacheEntry getEntry(String url) throws IOException {
- return entries.get(url);
+ public BasicHttpCache() {
+ this(new CacheConfig());
}
- /**
- * Removes a HttpCacheEntry from the cache
- *
- * @param url
- * Url that is the cache key
- */
- public synchronized void removeEntry(String url) throws IOException {
- entries.remove(url);
+ public void flushCacheEntriesFor(HttpHost host, HttpRequest request)
+ throws IOException {
+ String uri = uriExtractor.getURI(host, request);
+ storage.removeEntry(uri);
}
- public synchronized void updateEntry(
- String url,
- HttpCacheUpdateCallback callback) throws IOException {
- HttpCacheEntry existingEntry = entries.get(url);
- entries.put(url, callback.update(existingEntry));
+ void storeInCache(
+ HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
+ if (entry.hasVariants()) {
+ storeVariantEntry(target, request, entry);
+ } else {
+ storeNonVariantEntry(target, request, entry);
+ }
}
-}
+ void storeNonVariantEntry(
+ HttpHost target, HttpRequest req, HttpCacheEntry entry) throws IOException {
+ String uri = uriExtractor.getURI(target, req);
+ storage.putEntry(uri, entry);
+ }
+
+ void storeVariantEntry(
+ final HttpHost target,
+ final HttpRequest req,
+ final HttpCacheEntry entry) throws IOException {
+ final String parentURI = uriExtractor.getURI(target, req);
+ final String variantURI = uriExtractor.getVariantURI(target, req, entry);
+ storage.putEntry(variantURI, entry);
+
+ HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
+
+ public HttpCacheEntry update(HttpCacheEntry existing) throws IOException {
+ return doGetUpdatedParentEntry(
+ req.getRequestLine().getUri(), existing, entry, variantURI);
+ }
+
+ };
+ storage.updateEntry(parentURI, callback);
+ }
+
+
+ boolean isIncompleteResponse(HttpResponse resp, Resource resource) {
+ int status = resp.getStatusLine().getStatusCode();
+ if (status != HttpStatus.SC_OK
+ && status != HttpStatus.SC_PARTIAL_CONTENT) {
+ return false;
+ }
+ Header hdr = resp.getFirstHeader("Content-Length");
+ if (hdr == null) return false;
+ int contentLength;
+ try {
+ contentLength = Integer.parseInt(hdr.getValue());
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ return (resource.length() < contentLength);
+ }
+
+ HttpResponse generateIncompleteResponseError(HttpResponse response,
+ Resource resource) {
+ int contentLength = Integer.parseInt(response.getFirstHeader("Content-Length").getValue());
+ HttpResponse error =
+ new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
+ error.setHeader("Content-Type","text/plain;charset=UTF-8");
+ String msg = String.format("Received incomplete response " +
+ "with Content-Length %d but actual body length %d",
+ contentLength, resource.length());
+ byte[] msgBytes = msg.getBytes();
+ error.setHeader("Content-Length", Integer.toString(msgBytes.length));
+ error.setEntity(new ByteArrayEntity(msgBytes));
+ return error;
+ }
+
+ HttpCacheEntry doGetUpdatedParentEntry(
+ final String requestId,
+ final HttpCacheEntry existing,
+ final HttpCacheEntry entry,
+ final String variantURI) throws IOException {
+ HttpCacheEntry src = existing;
+ if (src == null) {
+ src = entry;
+ }
+ Set variants = new HashSet(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);
+
+ }
+
+}
\ No newline at end of file
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCacheStorage.java
new file mode 100644
index 000000000..2221cc0f7
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCacheStorage.java
@@ -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
+ * .
+ *
+ */
+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));
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
index c4f1a88bc..7fc1ccf53 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
@@ -37,7 +37,14 @@ public class CacheConfig {
*/
public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
+ /** Default setting for the maximum number of cache entries
+ * that will be retained.
+ */
+ public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000;
+
private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
+ private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
+
private boolean isSharedCache = true;
/**
@@ -74,4 +81,19 @@ public class CacheConfig {
this.isSharedCache = isSharedCache;
}
+ /**
+ * Returns the maximum number of cache entries the cache will retain.
+ * @return int
+ */
+ public int getMaxCacheEntries() {
+ return maxCacheEntries;
+ }
+
+ /**
+ * Sets the maximum number of cache entries the cache will retain.
+ * @param maxCacheEntries int
+ */
+ public void setMaxCacheEntries(int maxCacheEntries) {
+ this.maxCacheEntries = maxCacheEntries;
+ }
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
index 552755d15..7345bbace 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
@@ -40,6 +40,7 @@ import org.apache.http.annotation.ThreadSafe;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
/**
* Given a particular HttpRequest, flush any cache entries that this request
@@ -50,7 +51,7 @@ import org.apache.http.client.cache.HttpCacheEntry;
@ThreadSafe // so long as the cache implementation is thread-safe
class CacheInvalidator {
- private final HttpCache cache;
+ private final HttpCacheStorage storage;
private final URIExtractor uriExtractor;
private final Log log = LogFactory.getLog(getClass());
@@ -60,13 +61,13 @@ class CacheInvalidator {
* {@link URIExtractor}.
*
* @param uriExtractor Provides identifiers for the keys to store cache entries
- * @param cache the cache to store items away in
+ * @param storage the cache to store items away in
*/
public CacheInvalidator(
final URIExtractor uriExtractor,
- final HttpCache cache) {
+ final HttpCacheStorage storage) {
this.uriExtractor = uriExtractor;
- this.cache = cache;
+ this.storage = storage;
}
/**
@@ -82,15 +83,15 @@ class CacheInvalidator {
String theUri = uriExtractor.getURI(host, req);
- HttpCacheEntry parent = cache.getEntry(theUri);
+ HttpCacheEntry parent = storage.getEntry(theUri);
log.debug("parent entry: " + parent);
if (parent != null) {
for (String variantURI : parent.getVariantURIs()) {
- cache.removeEntry(variantURI);
+ storage.removeEntry(variantURI);
}
- cache.removeEntry(theUri);
+ storage.removeEntry(theUri);
}
URL reqURL;
try {
@@ -115,7 +116,7 @@ class CacheInvalidator {
protected void flushUriIfSameHost(URL requestURL, URL targetURL) throws IOException {
if (targetURL.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
- cache.removeEntry(targetURL.toString());
+ storage.removeEntry(targetURL.toString());
}
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
index 1161e0d22..534fa7928 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
@@ -29,14 +29,11 @@ package org.apache.http.impl.client.cache;
import java.io.IOException;
import java.net.URI;
import java.util.Date;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -52,12 +49,8 @@ import org.apache.http.client.ResponseHandler;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
-import org.apache.http.client.cache.HttpCacheUpdateCallback;
-import org.apache.http.client.cache.Resource;
-import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
@@ -70,7 +63,6 @@ import org.apache.http.protocol.HttpContext;
@ThreadSafe // So long as the responseCache implementation is threadsafe
public class CachingHttpClient implements HttpClient {
- private final static int MAX_CACHE_ENTRIES = 1000;
private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
private final AtomicLong cacheHits = new AtomicLong();
@@ -79,19 +71,14 @@ public class CachingHttpClient implements HttpClient {
private final HttpClient backend;
private final HttpCache responseCache;
- private final ResourceFactory resourceFactory;
private final CacheValidityPolicy validityPolicy;
private final ResponseCachingPolicy responseCachingPolicy;
- private final URIExtractor uriExtractor;
private final CachedHttpResponseGenerator responseGenerator;
- private final CacheInvalidator cacheInvalidator;
private final CacheableRequestPolicy cacheableRequestPolicy;
private final CachedResponseSuitabilityChecker suitabilityChecker;
private final ConditionalRequestBuilder conditionalRequestBuilder;
- private final CacheEntryUpdater cacheEntryUpdater;
-
private final int maxObjectSizeBytes;
private final boolean sharedCache;
@@ -103,7 +90,6 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient(
HttpClient client,
HttpCache cache,
- ResourceFactory resourceFactory,
CacheConfig config) {
super();
if (client == null) {
@@ -112,9 +98,6 @@ public class CachingHttpClient implements HttpClient {
if (cache == null) {
throw new IllegalArgumentException("HttpCache may not be null");
}
- if (resourceFactory == null) {
- throw new IllegalArgumentException("ResourceFactory may not be null");
- }
if (config == null) {
throw new IllegalArgumentException("CacheConfig may not be null");
}
@@ -122,16 +105,12 @@ public class CachingHttpClient implements HttpClient {
this.sharedCache = config.isSharedCache();
this.backend = client;
this.responseCache = cache;
- this.resourceFactory = resourceFactory;
this.validityPolicy = new CacheValidityPolicy();
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache);
this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
- this.uriExtractor = new URIExtractor();
- this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
this.cacheableRequestPolicy = new CacheableRequestPolicy();
this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy);
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
- this.cacheEntryUpdater = new CacheEntryUpdater(this.resourceFactory);
this.responseCompliance = new ResponseProtocolCompliance();
this.requestCompliance = new RequestProtocolCompliance();
@@ -139,91 +118,73 @@ public class CachingHttpClient implements HttpClient {
public CachingHttpClient() {
this(new DefaultHttpClient(),
- new BasicHttpCache(MAX_CACHE_ENTRIES),
- new HeapResourceFactory(),
+ new BasicHttpCache(),
new CacheConfig());
}
public CachingHttpClient(CacheConfig config) {
this(new DefaultHttpClient(),
- new BasicHttpCache(MAX_CACHE_ENTRIES),
- new HeapResourceFactory(),
+ new BasicHttpCache(config),
config);
}
public CachingHttpClient(HttpClient client) {
this(client,
- new BasicHttpCache(MAX_CACHE_ENTRIES),
- new HeapResourceFactory(),
+ new BasicHttpCache(),
new CacheConfig());
}
public CachingHttpClient(HttpClient client, CacheConfig config) {
this(client,
- new BasicHttpCache(MAX_CACHE_ENTRIES),
- new HeapResourceFactory(),
+ new BasicHttpCache(config),
config);
}
public CachingHttpClient(
- HttpCache cache,
- ResourceFactory resourceFactory) {
+ HttpCache cache) {
this(new DefaultHttpClient(),
cache,
- resourceFactory,
new CacheConfig());
}
public CachingHttpClient(
HttpCache cache,
- ResourceFactory resourceFactory,
CacheConfig config) {
this(new DefaultHttpClient(),
cache,
- resourceFactory,
config);
}
public CachingHttpClient(
HttpClient client,
- HttpCache cache,
- ResourceFactory resourceFactory) {
+ HttpCache cache) {
this(client,
cache,
- resourceFactory,
new CacheConfig());
}
CachingHttpClient(
HttpClient backend,
- ResourceFactory resourceFactory,
CacheValidityPolicy validityPolicy,
ResponseCachingPolicy responseCachingPolicy,
- URIExtractor uriExtractor,
HttpCache responseCache,
CachedHttpResponseGenerator responseGenerator,
- CacheInvalidator cacheInvalidator,
CacheableRequestPolicy cacheableRequestPolicy,
CachedResponseSuitabilityChecker suitabilityChecker,
ConditionalRequestBuilder conditionalRequestBuilder,
- CacheEntryUpdater entryUpdater,
ResponseProtocolCompliance responseCompliance,
RequestProtocolCompliance requestCompliance) {
CacheConfig config = new CacheConfig();
this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
this.sharedCache = config.isSharedCache();
this.backend = backend;
- this.resourceFactory = resourceFactory;
this.validityPolicy = validityPolicy;
this.responseCachingPolicy = responseCachingPolicy;
- this.uriExtractor = uriExtractor;
this.responseCache = responseCache;
this.responseGenerator = responseGenerator;
- this.cacheInvalidator = cacheInvalidator;
this.cacheableRequestPolicy = cacheableRequestPolicy;
this.suitabilityChecker = suitabilityChecker;
this.conditionalRequestBuilder = conditionalRequestBuilder;
- this.cacheEntryUpdater = entryUpdater;
this.responseCompliance = responseCompliance;
this.requestCompliance = requestCompliance;
}
@@ -409,13 +370,13 @@ public class CachingHttpClient implements HttpClient {
throw new ClientProtocolException(e);
}
- cacheInvalidator.flushInvalidatedCacheEntries(target, request);
+ responseCache.flushInvalidatedCacheEntriesFor(target, request);
if (!cacheableRequestPolicy.isServableFromCache(request)) {
return callBackend(target, request, context);
}
- HttpCacheEntry entry = getCacheEntry(target, request);
+ HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
if (entry == null) {
cacheMisses.getAndIncrement();
if (log.isDebugEnabled()) {
@@ -471,18 +432,6 @@ public class CachingHttpClient implements HttpClient {
return new Date();
}
- HttpCacheEntry getCacheEntry(HttpHost target, HttpRequest request) throws IOException {
- String uri = uriExtractor.getURI(target, request);
- HttpCacheEntry entry = responseCache.getEntry(uri);
-
- if (entry == null || !entry.hasVariants()) {
- return entry;
- }
-
- String variantUri = uriExtractor.getVariantURI(target, request, entry);
- return responseCache.getEntry(variantUri);
- }
-
boolean clientRequestsOurOptions(HttpRequest request) {
RequestLine line = request.getRequestLine();
@@ -533,101 +482,14 @@ public class CachingHttpClient implements HttpClient {
int statusCode = backendResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
cacheUpdates.getAndIncrement();
- HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(
- request.getRequestLine().getUri(),
- cacheEntry,
- requestDate,
- responseDate,
- backendResponse);
- storeInCache(target, request, updatedEntry);
- return responseGenerator.generateResponse(updatedEntry);
+ return responseCache.updateCacheEntry(target, request, cacheEntry,
+ backendResponse, requestDate, responseDate);
}
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
backendResponse);
}
- void storeInCache(
- HttpHost target, HttpRequest request, HttpCacheEntry entry) throws IOException {
- if (entry.hasVariants()) {
- storeVariantEntry(target, request, entry);
- } else {
- storeNonVariantEntry(target, request, entry);
- }
- }
-
- void storeNonVariantEntry(
- HttpHost target, HttpRequest req, HttpCacheEntry entry) throws IOException {
- String uri = uriExtractor.getURI(target, req);
- responseCache.putEntry(uri, entry);
- }
-
- void storeVariantEntry(
- final HttpHost target,
- final HttpRequest req,
- final HttpCacheEntry entry) throws IOException {
- final String parentURI = uriExtractor.getURI(target, req);
- final String variantURI = uriExtractor.getVariantURI(target, req, entry);
- responseCache.putEntry(variantURI, entry);
-
- HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
-
- public HttpCacheEntry update(HttpCacheEntry existing) throws IOException {
- return doGetUpdatedParentEntry(
- req.getRequestLine().getUri(), existing, entry, variantURI);
- }
-
- };
- responseCache.updateEntry(parentURI, callback);
- }
-
- HttpCacheEntry doGetUpdatedParentEntry(
- final String requestId,
- final HttpCacheEntry existing,
- final HttpCacheEntry entry,
- final String variantURI) throws IOException {
- HttpCacheEntry src = existing;
- if (src == null) {
- src = entry;
- }
- Set variants = new HashSet(src.getVariantURIs());
- variants.add(variantURI);
- Resource resource = resourceFactory.copy(requestId, src.getResource());
- return new HttpCacheEntry(
- src.getRequestDate(),
- src.getResponseDate(),
- src.getStatusLine(),
- src.getAllHeaders(),
- resource,
- variants);
- }
-
- HttpResponse correctIncompleteResponse(HttpResponse resp, Resource resource) {
- int status = resp.getStatusLine().getStatusCode();
- if (status != HttpStatus.SC_OK
- && status != HttpStatus.SC_PARTIAL_CONTENT) {
- return resp;
- }
- Header hdr = resp.getFirstHeader("Content-Length");
- if (hdr == null) return resp;
- int contentLength;
- try {
- contentLength = Integer.parseInt(hdr.getValue());
- } catch (NumberFormatException nfe) {
- return resp;
- }
- if (resource.length() >= contentLength) return resp;
- HttpResponse error =
- new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
- error.setHeader("Content-Type","text/plain;charset=UTF-8");
- String msg = String.format("Received incomplete response " +
- "with Content-Length %d but actual body length %d", contentLength, resource.length());
- byte[] msgBytes = msg.getBytes();
- error.setHeader("Content-Length", Integer.toString(msgBytes.length));
- error.setEntity(new ByteArrayEntity(msgBytes));
- return error;
- }
-
HttpResponse handleBackendResponse(
HttpHost target,
HttpRequest request,
@@ -640,40 +502,13 @@ public class CachingHttpClient implements HttpClient {
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
- HttpResponse corrected = backendResponse;
if (cacheable) {
-
- SizeLimitedResponseReader responseReader = getResponseReader(request, backendResponse);
- responseReader.readResponse();
-
- if (responseReader.isLimitReached()) {
- return responseReader.getReconstructedResponse();
- }
-
- Resource resource = responseReader.getResource();
- corrected = correctIncompleteResponse(backendResponse, resource);
- int correctedStatus = corrected.getStatusLine().getStatusCode();
- if (HttpStatus.SC_BAD_GATEWAY != correctedStatus) {
- HttpCacheEntry entry = new HttpCacheEntry(
- requestDate,
- responseDate,
- corrected.getStatusLine(),
- corrected.getAllHeaders(),
- resource,
- null);
- storeInCache(target, request, entry);
- return responseGenerator.generateResponse(entry);
- }
+ return responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate,
+ responseDate);
}
- String uri = uriExtractor.getURI(target, request);
- responseCache.removeEntry(uri);
- return corrected;
- }
-
- SizeLimitedResponseReader getResponseReader(HttpRequest request, HttpResponse backEndResponse) {
- return new SizeLimitedResponseReader(
- resourceFactory, maxObjectSizeBytes, request, backEndResponse);
+ responseCache.flushCacheEntriesFor(target, request);
+ return backendResponse;
}
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCacheStorage.java
similarity index 92%
rename from httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java
rename to httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCacheStorage.java
index cac9f5439..06563cff6 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ManagedHttpCacheStorage.java
@@ -33,13 +33,13 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.http.annotation.ThreadSafe;
-import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.Resource;
/**
- * {@link HttpCache} implementation capable of deallocating resources associated with
+ * {@link HttpCacheStorage} implementation capable of deallocating resources associated with
* the cache entries. This cache keeps track of cache entries using {@link PhantomReference}
* and maintains a collection of all resources that are no longer in use. The cache, however,
* does not automatically deallocates associated resources by invoking {@link Resource#dispose()}
@@ -47,13 +47,13 @@ import org.apache.http.client.cache.Resource;
* resource deallocation. The cache can be permanently shut down using {@link #shutdown()}
* method. All resources associated with the entries used by the cache will be deallocated.
*
- * This {@link HttpCache} implementation is intended for use with {@link FileCacheEntry}
+ * This {@link HttpCacheStorage} implementation is intended for use with {@link FileCacheEntry}
* and similar.
*
* @since 4.1
*/
@ThreadSafe
-public class ManagedHttpCache implements HttpCache {
+public class ManagedHttpCacheStorage implements HttpCacheStorage {
private final CacheMap entries;
private final ReferenceQueue morque;
@@ -61,9 +61,9 @@ public class ManagedHttpCache implements HttpCache {
private volatile boolean shutdown;
- public ManagedHttpCache(int maxEntries) {
+ public ManagedHttpCacheStorage(final CacheConfig config) {
super();
- this.entries = new CacheMap(maxEntries);
+ this.entries = new CacheMap(config.getMaxCacheEntries());
this.morque = new ReferenceQueue();
this.resources = new HashSet();
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java
index b142bb6c0..e102bdb64 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/SizeLimitedResponseReader.java
@@ -32,7 +32,6 @@ import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.client.cache.InputLimit;
import org.apache.http.client.cache.Resource;
@@ -116,8 +115,7 @@ class SizeLimitedResponseReader {
HttpResponse getReconstructedResponse() throws IOException {
ensureConsumed();
- HttpResponse reconstructed = new BasicHttpResponse(response.getProtocolVersion(),
- HttpStatus.SC_OK, "Success");
+ HttpResponse reconstructed = new BasicHttpResponse(response.getStatusLine());
reconstructed.setHeaders(response.getAllHeaders());
reconstructed.setEntity(new CombinedEntity(resource, instream));
return reconstructed;
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
similarity index 93%
rename from httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCache.java
rename to httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
index 49be4304b..69b80b4d4 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
@@ -31,15 +31,15 @@ import java.io.IOException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
-import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
-public class EhcacheHttpCache implements HttpCache {
+public class EhcacheHttpCacheStorage implements HttpCacheStorage {
private final Ehcache cache;
- public EhcacheHttpCache(Ehcache cache) {
+ public EhcacheHttpCacheStorage(Ehcache cache) {
this.cache = cache;
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
index 000d932f7..fe1a12233 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
@@ -48,12 +48,13 @@ public abstract class AbstractProtocolTest {
originResponse = make200Response();
- cache = new BasicHttpCache(MAX_ENTRIES);
+ params = new CacheConfig();
+ params.setMaxCacheEntries(MAX_ENTRIES);
+ params.setMaxObjectSizeBytes(MAX_BYTES);
+ cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
- params = new CacheConfig();
- params.setMaxObjectSizeBytes(MAX_BYTES);
- impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
+ impl = new CachingHttpClient(mockBackend, cache, params);
}
protected void replayMocks() {
@@ -89,18 +90,23 @@ public abstract class AbstractProtocolTest {
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
- impl = new CachingHttpClient(mockBackend, mockCache, new HeapResourceFactory(), params);
+ impl = new CachingHttpClient(mockBackend, mockCache, params);
- EasyMock.expect(mockCache.getEntry((String) EasyMock.anyObject())).andReturn(null)
- .anyTimes();
+ EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
+ .andReturn(null).anyTimes();
+ mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
+ EasyMock.expectLastCall().anyTimes();
- mockCache.removeEntry(EasyMock.isA(String.class));
+ mockCache.flushCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
+ EasyMock.expectLastCall().anyTimes();
+
+ mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
}
protected void behaveAsNonSharedCache() {
params.setSharedCache(false);
- impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
+ impl = new CachingHttpClient(mockBackend, cache, params);
}
public AbstractProtocolTest() {
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/CacheEntry.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/CacheEntry.java
index 4502a7c57..20c487957 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/CacheEntry.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/CacheEntry.java
@@ -27,6 +27,7 @@
package org.apache.http.impl.client.cache;
import java.util.Date;
+import java.util.Set;
import org.apache.http.Header;
import org.apache.http.client.cache.HttpCacheEntry;
@@ -78,4 +79,8 @@ public class CacheEntry extends HttpCacheEntry {
super(new Date(), new Date(), new OKStatus(), new Header[] {}, new HeapResource(content), null);
}
+ public CacheEntry(Set variants) {
+ super(new Date(), new Date(), new OKStatus(), new Header[] {}, BODY, variants);
+ }
+
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
index 594f5912b..1a92f382e 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
@@ -75,14 +75,15 @@ public class DoNotTestProtocolRequirements {
request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
originResponse = make200Response();
+ CacheConfig params = new CacheConfig();
+ params.setMaxObjectSizeBytes(MAX_BYTES);
+ params.setMaxCacheEntries(MAX_ENTRIES);
- HttpCache cache = new BasicHttpCache(MAX_ENTRIES);
+ HttpCache cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockEntity = EasyMock.createMock(HttpEntity.class);
mockCache = EasyMock.createMock(HttpCache.class);
- CacheConfig params = new CacheConfig();
- params.setMaxObjectSizeBytes(MAX_BYTES);
- impl = new CachingHttpClient(mockBackend, cache, new HeapResourceFactory(), params);
+ impl = new CachingHttpClient(mockBackend, cache, params);
}
private HttpResponse make200Response() {
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
index 2975b49f5..88a9ce914 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
@@ -195,9 +195,13 @@ public class HttpTestUtils {
*/
public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2)
throws Exception {
- return (equivalent(r1.getEntity(), r2.getEntity())
- && semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine()) && isEndToEndHeaderSubset(
- r1, r2));
+ final boolean entitiesEquivalent = equivalent(r1.getEntity(), r2.getEntity());
+ if (!entitiesEquivalent) return false;
+ final boolean statusLinesEquivalent = semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine());
+ if (!statusLinesEquivalent) return false;
+ final boolean e2eHeadersEquivalentSubset = isEndToEndHeaderSubset(
+ r1, r2);
+ return e2eHeadersEquivalentSubset;
}
/* Assert.asserts that two requests are morally equivalent. */
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/SimpleHttpCacheStorage.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/SimpleHttpCacheStorage.java
new file mode 100644
index 000000000..8cdfd1e34
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/SimpleHttpCacheStorage.java
@@ -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
+ * .
+ *
+ */
+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 map;
+
+ public SimpleHttpCacheStorage() {
+ map = new HashMap();
+ }
+
+ 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);
+ }
+
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java
new file mode 100644
index 000000000..bc0c11b6f
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java
@@ -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
+ * .
+ *
+ */
+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 existingVariants = new HashSet();
+ 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);
+ }
+
+
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
index 6182ef192..92aa6e94d 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
@@ -34,7 +34,7 @@ import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.ProtocolVersion;
-import org.apache.http.client.cache.HttpCache;
+import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.easymock.classextension.EasyMock;
@@ -46,7 +46,7 @@ public class TestCacheInvalidator {
private static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
private CacheInvalidator impl;
- private HttpCache mockCache;
+ private HttpCacheStorage mockStorage;
private HttpHost host;
private URIExtractor extractor;
private CacheEntry mockEntry;
@@ -54,20 +54,20 @@ public class TestCacheInvalidator {
@Before
public void setUp() {
host = new HttpHost("foo.example.com");
- mockCache = EasyMock.createMock(HttpCache.class);
+ mockStorage = EasyMock.createMock(HttpCacheStorage.class);
extractor = new URIExtractor();
mockEntry = EasyMock.createMock(CacheEntry.class);
- impl = new CacheInvalidator(extractor, mockCache);
+ impl = new CacheInvalidator(extractor, mockStorage);
}
private void replayMocks() {
- EasyMock.replay(mockCache);
+ EasyMock.replay(mockStorage);
EasyMock.replay(mockEntry);
}
private void verifyMocks() {
- EasyMock.verify(mockCache);
+ EasyMock.verify(mockStorage);
EasyMock.verify(mockEntry);
}
@@ -283,16 +283,16 @@ public class TestCacheInvalidator {
}
private void cacheReturnsEntryForUri(String theUri) throws IOException {
- org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andReturn(mockEntry);
+ org.easymock.EasyMock.expect(mockStorage.getEntry(theUri)).andReturn(mockEntry);
}
private void cacheReturnsExceptionForUri(String theUri) throws IOException {
- org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andThrow(
+ org.easymock.EasyMock.expect(mockStorage.getEntry(theUri)).andThrow(
new IOException("TOTAL FAIL"));
}
private void entryIsRemoved(String theUri) throws IOException {
- mockCache.removeEntry(theUri);
+ mockStorage.removeEntry(theUri);
}
}
\ No newline at end of file
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
index ce343a208..ba412ede5 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
@@ -27,14 +27,11 @@
package org.apache.http.impl.client.cache;
import java.io.IOException;
-import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -47,13 +44,9 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.cache.HttpCache;
import org.apache.http.client.cache.HttpCacheEntry;
-import org.apache.http.client.cache.Resource;
-import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
@@ -73,17 +66,9 @@ public class TestCachingHttpClient {
private static final String REVALIDATE_CACHE_ENTRY = "revalidateCacheEntry";
- private static final String GET_CACHE_ENTRY = "getCacheEntry";
-
- private static final String STORE_IN_CACHE = "storeInCache";
-
- private static final String GET_RESPONSE_READER = "getResponseReader";
-
private CachingHttpClient impl;
private boolean mockedImpl;
- private ResourceFactory mockResourceFactory;
- private CacheInvalidator mockInvalidator;
private CacheValidityPolicy mockValidityPolicy;
private CacheableRequestPolicy mockRequestPolicy;
private HttpClient mockBackend;
@@ -92,20 +77,14 @@ public class TestCachingHttpClient {
private ResponseCachingPolicy mockResponsePolicy;
private HttpResponse mockBackendResponse;
private CacheEntry mockCacheEntry;
- private CacheEntry mockVariantCacheEntry;
- private CacheEntry mockUpdatedCacheEntry;
- private URIExtractor mockExtractor;
private CachedHttpResponseGenerator mockResponseGenerator;
- private SizeLimitedResponseReader mockResponseReader;
private ClientConnectionManager mockConnectionManager;
private ResponseHandler