diff --git a/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java index 1c154fda9..968e56fcc 100644 --- a/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java +++ b/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java @@ -162,6 +162,7 @@ public class CachingHttpClient implements HttpClient { private final RequestProtocolCompliance requestCompliance; private final AsynchronousValidator asynchRevalidator; + private final boolean allowHeadResponseCaching; private final Log log = LogFactory.getLog(getClass()); @@ -179,7 +180,8 @@ public class CachingHttpClient implements HttpClient { this.responseCache = cache; this.validityPolicy = new CacheValidityPolicy(); this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache, - config.isNeverCacheHTTP10ResponsesWithQuery(), config.is303CachingEnabled()); + config.isNeverCacheHTTP10ResponsesWithQuery(), config.is303CachingEnabled(), + config.isHeadResponseCachingEnabled()); this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy); this.cacheableRequestPolicy = new CacheableRequestPolicy(); this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, config); @@ -189,6 +191,7 @@ public class CachingHttpClient implements HttpClient { this.requestCompliance = new RequestProtocolCompliance(config.isWeakETagOnPutDeleteAllowed()); this.asynchRevalidator = makeAsynchronousValidator(config); + this.allowHeadResponseCaching = config.isHeadResponseCachingEnabled(); } /** @@ -301,6 +304,7 @@ public class CachingHttpClient implements HttpClient { this.responseCompliance = responseCompliance; this.requestCompliance = requestCompliance; this.asynchRevalidator = makeAsynchronousValidator(config); + this.allowHeadResponseCaching = config.isHeadResponseCachingEnabled(); } private AsynchronousValidator makeAsynchronousValidator( @@ -445,7 +449,7 @@ public class CachingHttpClient implements HttpClient { flushEntriesInvalidatedByRequest(target, request); - if (!cacheableRequestPolicy.isServableFromCache(request)) { + if (!cacheableRequestPolicy.isServableFromCache(request, allowHeadResponseCaching)) { log.debug("Request is not servable from cache"); return callBackend(target, request, context); } @@ -595,7 +599,7 @@ public class CachingHttpClient implements HttpClient { || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) { cachedResponse = responseGenerator.generateNotModifiedResponse(entry); } else { - cachedResponse = responseGenerator.generateResponse(entry); + cachedResponse = responseGenerator.generateResponse(request, entry); } setResponseStatus(context, CacheResponseStatus.CACHE_HIT); if (validityPolicy.getStalenessSecs(entry, now) > 0L) { @@ -609,7 +613,7 @@ public class CachingHttpClient implements HttpClient { if (staleResponseNotAllowed(request, entry, now)) { return generateGatewayTimeout(context); } else { - return unvalidatedCacheHit(context, entry); + return unvalidatedCacheHit(request, context, entry); } } @@ -619,9 +623,11 @@ public class CachingHttpClient implements HttpClient { HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"); } - private HttpResponse unvalidatedCacheHit(final HttpContext context, + private HttpResponse unvalidatedCacheHit( + final HttpRequestWrapper request, + final HttpContext context, final HttpCacheEntry entry) { - final HttpResponse cachedResponse = responseGenerator.generateResponse(entry); + final HttpResponse cachedResponse = responseGenerator.generateResponse(request, entry); setResponseStatus(context, CacheResponseStatus.CACHE_HIT); cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\""); return cachedResponse; @@ -819,7 +825,7 @@ public class CachingHttpClient implements HttpClient { conditionalRequest, requestDate, responseDate, backendResponse, matchingVariant, matchedEntry); - final HttpResponse resp = responseGenerator.generateResponse(responseEntry); + final HttpResponse resp = responseGenerator.generateResponse(request, responseEntry); tryToUpdateVariantMap(target, request, matchingVariant); if (shouldSendNotModifiedResponse(request, responseEntry)) { @@ -901,13 +907,13 @@ public class CachingHttpClient implements HttpClient { && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) { return responseGenerator.generateNotModifiedResponse(updatedEntry); } - return responseGenerator.generateResponse(updatedEntry); + return responseGenerator.generateResponse(request, updatedEntry); } if (staleIfErrorAppliesTo(statusCode) && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate()) && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) { - final HttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry); + final HttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry); cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\""); final HttpEntity errorBody = backendResponse.getEntity(); if (errorBody != null) { diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java index 38ef49f53..947365866 100644 --- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java +++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java @@ -62,6 +62,7 @@ public class HttpCacheEntry implements Serializable { private final Resource resource; private final Map variantMap; private final Date date; + private final String requestMethod; /** * Create a new {@link HttpCacheEntry} with variants. @@ -80,6 +81,7 @@ public class HttpCacheEntry implements Serializable { * of this parent entry; this maps a "variant key" (derived * from the varying request headers) to a "cache key" (where * in the cache storage the particular variant is located) + * @param requestMethod HTTP method used when the request was made */ public HttpCacheEntry( final Date requestDate, @@ -87,7 +89,8 @@ public class HttpCacheEntry implements Serializable { final StatusLine statusLine, final Header[] responseHeaders, final Resource resource, - final Map variantMap) { + final Map variantMap, + final String requestMethod) { super(); Args.notNull(requestDate, "Request date"); Args.notNull(responseDate, "Response date"); @@ -103,6 +106,7 @@ public class HttpCacheEntry implements Serializable { ? new HashMap(variantMap) : null; this.date = parseDate(); + this.requestMethod = requestMethod; } /** @@ -119,11 +123,12 @@ public class HttpCacheEntry implements Serializable { * @param responseHeaders * Header[] from original HTTP Response * @param resource representing origin response body + * @param requestMethod HTTP method used when the request was made */ public HttpCacheEntry(final Date requestDate, final Date responseDate, final StatusLine statusLine, - final Header[] responseHeaders, final Resource resource) { + final Header[] responseHeaders, final Resource resource, final String requestMethod) { this(requestDate, responseDate, statusLine, responseHeaders, resource, - new HashMap()); + new HashMap(), requestMethod); } /** @@ -250,6 +255,16 @@ public class HttpCacheEntry implements Serializable { return Collections.unmodifiableMap(variantMap); } + /** + * Returns the HTTP request method that was used to create the cached + * response entry. + * + * @since 4.4 + */ + public String getRequestMethod() { + return requestMethod; + } + /** * Provides a string representation of this instance suitable for * human consumption. 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 91e692d7a..aa327f0c4 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 @@ -51,6 +51,7 @@ import org.apache.http.client.cache.HttpCacheUpdateException; import org.apache.http.client.cache.Resource; import org.apache.http.client.cache.ResourceFactory; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.message.BasicHttpResponse; import org.apache.http.protocol.HTTP; @@ -68,6 +69,7 @@ class BasicHttpCache implements HttpCache { private final CachedHttpResponseGenerator responseGenerator; private final HttpCacheInvalidator cacheInvalidator; private final HttpCacheStorage storage; + private final boolean allowHeadResponseCaching; private final Log log = LogFactory.getLog(getClass()); @@ -84,6 +86,7 @@ class BasicHttpCache implements HttpCache { this.responseGenerator = new CachedHttpResponseGenerator(); this.storage = storage; this.cacheInvalidator = cacheInvalidator; + this.allowHeadResponseCaching = config.isHeadResponseCachingEnabled(); } public BasicHttpCache( @@ -92,7 +95,7 @@ class BasicHttpCache implements HttpCache { final CacheConfig config, final CacheKeyGenerator uriExtractor) { this( resourceFactory, storage, config, uriExtractor, - new CacheInvalidator(uriExtractor, storage)); + new CacheInvalidator(uriExtractor, storage, config.isHeadResponseCachingEnabled())); } public BasicHttpCache( @@ -208,6 +211,9 @@ class BasicHttpCache implements HttpCache { } catch (final NumberFormatException nfe) { return false; } + if (resource == null) { + return false; + } return (resource.length() < contentLength); } @@ -219,7 +225,7 @@ class BasicHttpCache implements HttpCache { error.setHeader("Content-Type","text/plain;charset=UTF-8"); final String msg = String.format("Received incomplete response " + "with Content-Length %d but actual body length %d", - contentLength, Long.valueOf(resource.length())); + contentLength, resource.length()); final byte[] msgBytes = msg.getBytes(); error.setHeader("Content-Length", Integer.toString(msgBytes.length)); error.setEntity(new ByteArrayEntity(msgBytes)); @@ -249,7 +255,8 @@ class BasicHttpCache implements HttpCache { src.getStatusLine(), src.getAllHeaders(), resource, - variantMap); + variantMap, + allowHeadResponseCaching ? src.getRequestMethod() : null); } @Override @@ -261,7 +268,8 @@ class BasicHttpCache implements HttpCache { stale, requestSent, responseReceived, - originResponse); + originResponse, + allowHeadResponseCaching); storeInCache(target, request, updatedEntry); return updatedEntry; } @@ -275,7 +283,8 @@ class BasicHttpCache implements HttpCache { stale, requestSent, responseReceived, - originResponse); + originResponse, + allowHeadResponseCaching); storage.putEntry(cacheKey, updatedEntry); return updatedEntry; } @@ -317,9 +326,10 @@ class BasicHttpCache implements HttpCache { responseReceived, originResponse.getStatusLine(), originResponse.getAllHeaders(), - resource); + resource, + allowHeadResponseCaching ? request.getRequestLine().getMethod() : null); storeInCache(host, request, entry); - return responseGenerator.generateResponse(entry); + return responseGenerator.generateResponse(HttpRequestWrapper.wrap(request, host), entry); } finally { if (closeOriginResponse) { originResponse.close(); 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 3c4a55e7a..b08fef749 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 @@ -175,6 +175,7 @@ public class CacheConfig implements Cloneable { private int asynchronousWorkerIdleLifetimeSecs; private int revalidationQueueSize; private boolean neverCacheHTTP10ResponsesWithQuery; + private final boolean allowHeadResponseCaching; /** * @deprecated (4.3) use {@link Builder}. @@ -195,6 +196,7 @@ public class CacheConfig implements Cloneable { this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + this.allowHeadResponseCaching = false; } CacheConfig( @@ -211,7 +213,8 @@ public class CacheConfig implements Cloneable { final int asynchronousWorkersCore, final int asynchronousWorkerIdleLifetimeSecs, final int revalidationQueueSize, - final boolean neverCacheHTTP10ResponsesWithQuery) { + final boolean neverCacheHTTP10ResponsesWithQuery, + final boolean allowHeadResponseCaching) { super(); this.maxObjectSize = maxObjectSize; this.maxCacheEntries = maxCacheEntries; @@ -226,6 +229,7 @@ public class CacheConfig implements Cloneable { this.asynchronousWorkersCore = asynchronousWorkersCore; this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; this.revalidationQueueSize = revalidationQueueSize; + this.allowHeadResponseCaching = allowHeadResponseCaching; } /** @@ -500,6 +504,14 @@ public class CacheConfig implements Cloneable { return revalidationQueueSize; } + /** + * Returns whether HEAD response caching is enabled. + * @return {@code true} if it is enabled. + */ + public boolean isHeadResponseCachingEnabled() { + return allowHeadResponseCaching; + } + /** * Sets the current maximum queue size for background revalidations. * @@ -533,6 +545,7 @@ public class CacheConfig implements Cloneable { .setAsynchronousWorkersCore(config.getAsynchronousWorkersCore()) .setAsynchronousWorkerIdleLifetimeSecs(config.getAsynchronousWorkerIdleLifetimeSecs()) .setRevalidationQueueSize(config.getRevalidationQueueSize()) + .setAllowHeadResponseCaching(config.isHeadResponseCachingEnabled()) .setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery()); } @@ -553,6 +566,7 @@ public class CacheConfig implements Cloneable { private int asynchronousWorkerIdleLifetimeSecs; private int revalidationQueueSize; private boolean neverCacheHTTP10ResponsesWithQuery; + private boolean allowHeadResponseCaching; Builder() { this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES; @@ -568,6 +582,7 @@ public class CacheConfig implements Cloneable { this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + this.allowHeadResponseCaching = false; } /** @@ -707,6 +722,17 @@ public class CacheConfig implements Cloneable { return this; } + /** + * Sets whether responses to HEAD requests should be cached or not. + * @param allowHeadResponseCaching should be {@code true} to + * permit HEAD response caching, {@code false} to disable it. + * @param allowHeadResponseCaching + */ + public Builder setAllowHeadResponseCaching(final boolean allowHeadResponseCaching) { + this.allowHeadResponseCaching = allowHeadResponseCaching; + return this; + } + /** * Sets whether the cache should never cache HTTP 1.0 responses with a query string or not. * @param neverCacheHTTP10ResponsesWithQuery true to never cache responses with a query @@ -735,7 +761,8 @@ public class CacheConfig implements Cloneable { asynchronousWorkersCore, asynchronousWorkerIdleLifetimeSecs, revalidationQueueSize, - neverCacheHTTP10ResponsesWithQuery); + neverCacheHTTP10ResponsesWithQuery, + allowHeadResponseCaching); } } @@ -757,6 +784,7 @@ public class CacheConfig implements Cloneable { .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs) .append(", revalidationQueueSize=").append(this.revalidationQueueSize) .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery) + .append(", headResponseCachingEnabled=").append(this.allowHeadResponseCaching) .append("]"); return builder.toString(); } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java index ffc528b3c..8fc58b380 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java @@ -75,6 +75,7 @@ class CacheEntryUpdater { * @param requestDate When the request was performed * @param responseDate When the response was gotten * @param response The HttpResponse from the backend server call + * @param allowHeadResponseCaching Should the cache entry include the request method * @return HttpCacheEntry an updated version of the cache entry * @throws java.io.IOException if something bad happens while trying to read the body from the original entry */ @@ -83,7 +84,8 @@ class CacheEntryUpdater { final HttpCacheEntry entry, final Date requestDate, final Date responseDate, - final HttpResponse response) throws IOException { + final HttpResponse response, + final boolean allowHeadResponseCaching) throws IOException { Args.check(response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED, "Response must have 304 status code"); final Header[] mergedHeaders = mergeHeaders(entry, response); @@ -96,7 +98,8 @@ class CacheEntryUpdater { responseDate, entry.getStatusLine(), mergedHeaders, - resource); + resource, + allowHeadResponseCaching ? entry.getRequestMethod() : null); } protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) { 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 741337245..1ffceee6e 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 @@ -56,6 +56,7 @@ class CacheInvalidator implements HttpCacheInvalidator { private final HttpCacheStorage storage; private final CacheKeyGenerator cacheKeyGenerator; + private final boolean allowHeadResponseCaching; private final Log log = LogFactory.getLog(getClass()); @@ -65,12 +66,15 @@ class CacheInvalidator implements HttpCacheInvalidator { * * @param uriExtractor Provides identifiers for the keys to store cache entries * @param storage the cache to store items away in + * @param allowHeadResponseCaching is HEAD response caching enabled */ public CacheInvalidator( final CacheKeyGenerator uriExtractor, - final HttpCacheStorage storage) { + final HttpCacheStorage storage, + final boolean allowHeadResponseCaching) { this.cacheKeyGenerator = uriExtractor; this.storage = storage; + this.allowHeadResponseCaching = allowHeadResponseCaching; } /** @@ -82,15 +86,11 @@ class CacheInvalidator implements HttpCacheInvalidator { */ @Override public void flushInvalidatedCacheEntries(final HttpHost host, final HttpRequest req) { - if (requestShouldNotBeCached(req)) { - log.debug("Request should not be cached"); - - final String theUri = cacheKeyGenerator.getURI(host, req); - - final HttpCacheEntry parent = getEntry(theUri); - - log.debug("parent entry: " + parent); + final String theUri = cacheKeyGenerator.getURI(host, req); + final HttpCacheEntry parent = getEntry(theUri); + if (requestShouldNotBeCached(req) || shouldInvalidateHeadCacheEntry(req, parent)) { + log.debug("Invalidating parent cache entry: " + parent); if (parent != null) { for (final String variantURI : parent.getVariantMap().values()) { flushEntry(variantURI); @@ -116,6 +116,18 @@ class CacheInvalidator implements HttpCacheInvalidator { } } + private boolean shouldInvalidateHeadCacheEntry(final HttpRequest req, final HttpCacheEntry parentCacheEntry) { + return allowHeadResponseCaching && requestIsGet(req) && isAHeadCacheEntry(parentCacheEntry); + } + + private boolean requestIsGet(final HttpRequest req) { + return req.getRequestLine().getMethod().equals((HeaderConstants.GET_METHOD)); + } + + private boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) { + return parentCacheEntry != null && parentCacheEntry.getRequestMethod().equals(HeaderConstants.HEAD_METHOD); + } + private void flushEntry(final String uri) { try { storage.removeEntry(uri); diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java index 05aa493e5..7690e8aff 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java @@ -214,7 +214,8 @@ class CacheValidityPolicy { * @return boolean indicating whether actual length matches Content-Length */ protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) { - return !hasContentLengthHeader(entry) || getContentLengthValue(entry) == entry.getResource().length(); + return !hasContentLengthHeader(entry) || + (entry.getResource() != null && getContentLengthValue(entry) == entry.getResource().length()); } protected long getApparentAgeSecs(final HttpCacheEntry entry) { diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java index f10ff3aef..c923156ba 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java @@ -49,11 +49,11 @@ class CacheableRequestPolicy { /** * Determines if an HttpRequest can be served from the cache. * - * @param request - * an HttpRequest + * @param request an HttpRequest + * @param allowHeadResponseCaching is HEAD response caching enabled * @return boolean Is it possible to serve this request from cache */ - public boolean isServableFromCache(final HttpRequest request) { + public boolean isServableFromCache(final HttpRequest request, final boolean allowHeadResponseCaching) { final String method = request.getRequestLine().getMethod(); final ProtocolVersion pv = request.getRequestLine().getProtocolVersion(); @@ -62,8 +62,9 @@ class CacheableRequestPolicy { return false; } - if (!method.equals(HeaderConstants.GET_METHOD)) { - log.trace("non-GET request was not serveable from cache"); + if (!(method.equals(HeaderConstants.GET_METHOD) || + (allowHeadResponseCaching && method.equals(HeaderConstants.HEAD_METHOD)))) { + log.trace("non-GET or non-HEAD request was not serveable from cache"); return false; } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java index 523c28f9d..f74a2e42c 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java @@ -37,6 +37,7 @@ import org.apache.http.annotation.Immutable; import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HttpCacheEntry; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.utils.DateUtils; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; @@ -64,19 +65,18 @@ class CachedHttpResponseGenerator { /** * If I was able to use a {@link CacheEntity} to response to the {@link org.apache.http.HttpRequest} then * generate an {@link HttpResponse} based on the cache entry. - * @param entry - * {@link CacheEntity} to transform into an {@link HttpResponse} + * @param request {@link HttpRequestWrapper} to generate the response for + * @param entry {@link CacheEntity} to transform into an {@link HttpResponse} * @return {@link HttpResponse} that was constructed */ - CloseableHttpResponse generateResponse(final HttpCacheEntry entry) { - + CloseableHttpResponse generateResponse(final HttpRequestWrapper request, final HttpCacheEntry entry) { final Date now = new Date(); final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, entry .getStatusCode(), entry.getReasonPhrase()); response.setHeaders(entry.getAllHeaders()); - if (entry.getResource() != null) { + if (responseShouldContainEntity(request, entry)) { final HttpEntity entity = new CacheEntity(entry); addMissingContentLengthHeader(response, entity); response.setEntity(entity); @@ -163,4 +163,10 @@ class CachedHttpResponseGenerator { final Header hdr = response.getFirstHeader(HTTP.TRANSFER_ENCODING); return hdr != null; } + + private boolean responseShouldContainEntity(final HttpRequestWrapper request, final HttpCacheEntry cacheEntry) { + return request.getRequestLine().getMethod().equals(HeaderConstants.GET_METHOD) && + cacheEntry.getResource() != null; + } + } diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java index 7391a72b7..c9784b7c2 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java @@ -56,6 +56,7 @@ class CachedResponseSuitabilityChecker { private final float heuristicCoefficient; private final long heuristicDefaultLifetime; private final CacheValidityPolicy validityStrategy; + private final boolean allowHeadResponseCaching; CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy, final CacheConfig config) { @@ -65,6 +66,7 @@ class CachedResponseSuitabilityChecker { this.useHeuristicCaching = config.isHeuristicCachingEnabled(); this.heuristicCoefficient = config.getHeuristicCoefficient(); this.heuristicDefaultLifetime = config.getHeuristicDefaultLifetime(); + this.allowHeadResponseCaching = config.isHeadResponseCachingEnabled(); } CachedResponseSuitabilityChecker(final CacheConfig config) { @@ -143,13 +145,12 @@ class CachedResponseSuitabilityChecker { * @return boolean yes/no answer */ public boolean canCachedResponseBeUsed(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry, final Date now) { - if (!isFreshEnough(entry, request, now)) { log.trace("Cache entry was not fresh enough"); return false; } - if (!validityStrategy.contentLengthHeaderMatchesActualLength(entry)) { + if (isGet(request) && !validityStrategy.contentLengthHeaderMatchesActualLength(entry)) { log.debug("Cache entry Content-Length and header information do not match"); return false; } @@ -160,13 +161,19 @@ class CachedResponseSuitabilityChecker { } if (!isConditional(request) && entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { - return false; + return false; } if (isConditional(request) && !allConditionalsMatch(request, entry, now)) { return false; } + if (allowHeadResponseCaching && hasUnsupportedCacheEntryForGet(request, entry)) { + log.debug("HEAD response caching enabled but the cache entry does not contain a " + + "request method, entity or a 204 response"); + return false; + } + for (final Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { for (final HeaderElement elt : ccHdr.getElements()) { if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { @@ -233,6 +240,22 @@ class CachedResponseSuitabilityChecker { return true; } + private boolean isGet(final HttpRequest request) { + return request.getRequestLine().getMethod().equals(HeaderConstants.GET_METHOD); + } + + private boolean entryIsNotA204Response(final HttpCacheEntry entry) { + return entry.getStatusCode() != HttpStatus.SC_NO_CONTENT; + } + + private boolean cacheEntryDoesNotContainMethodAndEntity(final HttpCacheEntry entry) { + return entry.getRequestMethod() == null && entry.getResource() == null; + } + + private boolean hasUnsupportedCacheEntryForGet(final HttpRequest request, final HttpCacheEntry entry) { + return isGet(request) && cacheEntryDoesNotContainMethodAndEntity(entry) && entryIsNotA204Response(entry); + } + /** * Is this request the type of conditional request we support? * @param request The current httpRequest being made diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java index 822c3408c..9774587e9 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java @@ -153,7 +153,8 @@ public class CachingExec implements ClientExecChain { this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed()); this.responseCachingPolicy = new ResponseCachingPolicy( this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(), - this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled()); + this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled(), + this.cacheConfig.isHeadResponseCachingEnabled()); this.asynchRevalidator = asynchRevalidator; } @@ -264,7 +265,7 @@ public class CachingExec implements ClientExecChain { flushEntriesInvalidatedByRequest(context.getTargetHost(), request); - if (!cacheableRequestPolicy.isServableFromCache(request)) { + if (!cacheableRequestPolicy.isServableFromCache(request, cacheConfig.isHeadResponseCachingEnabled())) { log.debug("Request is not servable from cache"); return callBackend(route, request, context, execAware); } @@ -423,14 +424,17 @@ public class CachingExec implements ClientExecChain { } } - private CloseableHttpResponse generateCachedResponse(final HttpRequestWrapper request, - final HttpContext context, final HttpCacheEntry entry, final Date now) { + private CloseableHttpResponse generateCachedResponse( + final HttpRequestWrapper request, + final HttpContext context, + final HttpCacheEntry entry, + final Date now) { final CloseableHttpResponse cachedResponse; if (request.containsHeader(HeaderConstants.IF_NONE_MATCH) || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) { cachedResponse = responseGenerator.generateNotModifiedResponse(entry); } else { - cachedResponse = responseGenerator.generateResponse(entry); + cachedResponse = responseGenerator.generateResponse(request, entry); } setResponseStatus(context, CacheResponseStatus.CACHE_HIT); if (validityPolicy.getStalenessSecs(entry, now) > 0L) { @@ -447,7 +451,7 @@ public class CachingExec implements ClientExecChain { if (staleResponseNotAllowed(request, entry, now)) { return generateGatewayTimeout(context); } else { - return unvalidatedCacheHit(context, entry); + return unvalidatedCacheHit(request, context, entry); } } @@ -460,8 +464,10 @@ public class CachingExec implements ClientExecChain { } private CloseableHttpResponse unvalidatedCacheHit( - final HttpContext context, final HttpCacheEntry entry) { - final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(entry); + final HttpRequestWrapper request, + final HttpContext context, + final HttpCacheEntry entry) { + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(request, entry); setResponseStatus(context, CacheResponseStatus.CACHE_HIT); cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\""); return cachedResponse; @@ -526,8 +532,8 @@ public class CachingExec implements ClientExecChain { final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; String value; - final Integer major = Integer.valueOf(pv.getMajor()); - final Integer minor = Integer.valueOf(pv.getMinor()); + final int major = pv.getMajor(); + final int minor = pv.getMinor(); if ("http".equalsIgnoreCase(pv.getProtocol())) { value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", major, minor, release); @@ -676,7 +682,7 @@ public class CachingExec implements ClientExecChain { backendResponse, matchingVariant, matchedEntry); backendResponse.close(); - final CloseableHttpResponse resp = responseGenerator.generateResponse(responseEntry); + final CloseableHttpResponse resp = responseGenerator.generateResponse(request, responseEntry); tryToUpdateVariantMap(context.getTargetHost(), request, matchingVariant); if (shouldSendNotModifiedResponse(request, responseEntry)) { @@ -788,14 +794,14 @@ public class CachingExec implements ClientExecChain { return responseGenerator .generateNotModifiedResponse(updatedEntry); } - return responseGenerator.generateResponse(updatedEntry); + return responseGenerator.generateResponse(request, updatedEntry); } if (staleIfErrorAppliesTo(statusCode) && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate()) && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) { try { - final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry); + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry); cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\""); return cachedResponse; } finally { diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClientBuilder.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClientBuilder.java index ef32df095..e471d2c80 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClientBuilder.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClientBuilder.java @@ -140,7 +140,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder { HttpCacheInvalidator cacheInvalidator = this.httpCacheInvalidator; if (cacheInvalidator == null) { - cacheInvalidator = new CacheInvalidator(uriExtractor, storageCopy); + cacheInvalidator = new CacheInvalidator(uriExtractor, storageCopy, config.isHeadResponseCachingEnabled()); } return new CachingExec(mainExec, diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java index 1a9a6d3bb..f62def634 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java @@ -67,6 +67,8 @@ class ResponseCachingPolicy { HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_GONE)); private final Set uncacheableStatuses; + private final boolean allowHeadResponseCaching; + /** * Define a cache policy that limits the size of things that should be stored * in the cache to a maximum of {@link HttpResponse} bytes in size. @@ -77,11 +79,13 @@ class ResponseCachingPolicy { * @param neverCache1_0ResponsesWithQueryString true to never cache HTTP 1.0 responses with a query string, false * to cache if explicit cache headers are found. * @param allow303Caching if this policy is permitted to cache 303 response + * @param allowHeadResponseCaching is HEAD response caching enabled */ public ResponseCachingPolicy(final long maxObjectSizeBytes, final boolean sharedCache, final boolean neverCache1_0ResponsesWithQueryString, - final boolean allow303Caching) { + final boolean allow303Caching, + final boolean allowHeadResponseCaching) { this.maxObjectSizeBytes = maxObjectSizeBytes; this.sharedCache = sharedCache; this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString; @@ -92,6 +96,7 @@ class ResponseCachingPolicy { uncacheableStatuses = new HashSet(Arrays.asList( HttpStatus.SC_PARTIAL_CONTENT, HttpStatus.SC_SEE_OTHER)); } + this.allowHeadResponseCaching = allowHeadResponseCaching; } /** @@ -104,7 +109,8 @@ class ResponseCachingPolicy { public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) { boolean cacheable = false; - if (!HeaderConstants.GET_METHOD.equals(httpMethod)) { + if (!(HeaderConstants.GET_METHOD.equals(httpMethod) || + (allowHeadResponseCaching && HeaderConstants.HEAD_METHOD.equals(httpMethod)))) { log.debug("Response was not cacheable."); return false; } diff --git a/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java b/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java index 737d9e9b2..197cfd5f9 100644 --- a/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java +++ b/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java @@ -70,7 +70,7 @@ public class TestHttpCacheEntry { private HttpCacheEntry makeEntry(final Header[] headers) { return new HttpCacheEntry(elevenSecondsAgo, nineSecondsAgo, - statusLine, headers, mockResource); + statusLine, headers, mockResource, HeaderConstants.GET_METHOD); } @Test @@ -148,7 +148,7 @@ public class TestHttpCacheEntry { public void mustProvideRequestDate() { try { new HttpCacheEntry(null, new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); fail("Should have thrown exception"); } catch (final IllegalArgumentException expected) { } @@ -159,7 +159,7 @@ public class TestHttpCacheEntry { public void mustProvideResponseDate() { try { new HttpCacheEntry(new Date(), null, statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); fail("Should have thrown exception"); } catch (final IllegalArgumentException expected) { } @@ -170,7 +170,7 @@ public class TestHttpCacheEntry { public void mustProvideStatusLine() { try { new HttpCacheEntry(new Date(), new Date(), null, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); fail("Should have thrown exception"); } catch (final IllegalArgumentException expected) { } @@ -181,7 +181,7 @@ public class TestHttpCacheEntry { public void mustProvideResponseHeaders() { try { new HttpCacheEntry(new Date(), new Date(), statusLine, - null, mockResource); + null, mockResource, HeaderConstants.GET_METHOD); fail("Should have thrown exception"); } catch (final IllegalArgumentException expected) { } @@ -190,14 +190,14 @@ public class TestHttpCacheEntry { @Test public void canRetrieveOriginalStatusLine() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(statusLine, entry.getStatusLine()); } @Test public void protocolVersionComesFromOriginalStatusLine() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(statusLine.getProtocolVersion(), entry.getProtocolVersion()); } @@ -205,14 +205,14 @@ public class TestHttpCacheEntry { @Test public void reasonPhraseComesFromOriginalStatusLine() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(statusLine.getReasonPhrase(), entry.getReasonPhrase()); } @Test public void statusCodeComesFromOriginalStatusLine() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertEquals(statusLine.getStatusCode(), entry.getStatusCode()); } @@ -220,7 +220,7 @@ public class TestHttpCacheEntry { public void canGetOriginalRequestDate() { final Date requestDate = new Date(); entry = new HttpCacheEntry(requestDate, new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(requestDate, entry.getRequestDate()); } @@ -228,14 +228,14 @@ public class TestHttpCacheEntry { public void canGetOriginalResponseDate() { final Date responseDate = new Date(); entry = new HttpCacheEntry(new Date(), responseDate, statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(responseDate, entry.getResponseDate()); } @Test public void canGetOriginalResource() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertSame(mockResource, entry.getResource()); } @@ -246,7 +246,7 @@ public class TestHttpCacheEntry { new BasicHeader("Date", DateUtils.formatDate(now)) }; entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - headers, mockResource); + headers, mockResource, HeaderConstants.GET_METHOD); final Header[] result = entry.getAllHeaders(); assertEquals(headers.length, result.length); for(int i=0; i()); + new HashMap(), HeaderConstants.GET_METHOD); } @Test @@ -276,7 +276,7 @@ public class TestHttpCacheEntry { variantMap.put("C","D"); entry = new HttpCacheEntry(new Date(), new Date(), statusLine, new Header[]{}, mockResource, - variantMap); + variantMap, HeaderConstants.GET_METHOD); final Map result = entry.getVariantMap(); assertEquals(2, result.size()); assertEquals("B", result.get("A")); @@ -290,7 +290,7 @@ public class TestHttpCacheEntry { variantMap.put("C","D"); entry = new HttpCacheEntry(new Date(), new Date(), statusLine, new Header[]{}, mockResource, - variantMap); + variantMap, HeaderConstants.GET_METHOD); final Map result = entry.getVariantMap(); try { result.remove("A"); @@ -307,7 +307,7 @@ public class TestHttpCacheEntry { @Test public void canConvertToString() { entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - new Header[]{}, mockResource); + new Header[]{}, mockResource, HeaderConstants.GET_METHOD); assertNotNull(entry.toString()); assertFalse("".equals(entry.toString())); } @@ -316,7 +316,7 @@ public class TestHttpCacheEntry { public void testMissingDateHeaderIsIgnored() { final Header[] headers = new Header[] {}; entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - headers, mockResource); + headers, mockResource, HeaderConstants.GET_METHOD); assertNull(entry.getDate()); } @@ -324,7 +324,7 @@ public class TestHttpCacheEntry { public void testMalformedDateHeaderIsIgnored() { final Header[] headers = new Header[] { new BasicHeader("Date", "asdf") }; entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - headers, mockResource); + headers, mockResource, HeaderConstants.GET_METHOD); assertNull(entry.getDate()); } @@ -335,10 +335,19 @@ public class TestHttpCacheEntry { final Date date = new Date(nowMs - (nowMs % 1000L)); final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatDate(date)) }; entry = new HttpCacheEntry(new Date(), new Date(), statusLine, - headers, mockResource); + headers, mockResource, HeaderConstants.GET_METHOD); final Date dateHeaderValue = entry.getDate(); assertNotNull(dateHeaderValue); assertEquals(date.getTime(), dateHeaderValue.getTime()); } + @Test + public void testGetMethodReturnsCorrectRequestMethod() { + final Header[] headers = { new BasicHeader("foo", "fooValue"), + new BasicHeader("bar", "barValue1"), + new BasicHeader("bar", "barValue2") + }; + entry = makeEntry(headers); + assertEquals(HeaderConstants.GET_METHOD, entry.getRequestMethod()); + } } 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 3d9fb547d..d1df50557 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 @@ -28,6 +28,7 @@ package org.apache.http.impl.client.cache; import java.io.InputStream; import java.util.Date; +import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -41,6 +42,7 @@ import org.apache.http.HttpStatus; import org.apache.http.HttpVersion; import org.apache.http.RequestLine; import org.apache.http.StatusLine; +import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HttpCacheEntry; import org.apache.http.client.utils.DateUtils; import org.apache.http.entity.ByteArrayEntity; @@ -299,8 +301,7 @@ public class HttpTestUtils { public static HttpCacheEntry makeCacheEntry(final Date requestDate, final Date responseDate, final Header[] headers, final byte[] bytes, final Map variantMap) { - final StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - return new HttpCacheEntry(requestDate, responseDate, statusLine, headers, new HeapResource(bytes), variantMap); + return new HttpCacheEntry(requestDate, responseDate, makeStatusLine(), headers, new HeapResource(bytes), variantMap, HeaderConstants.GET_METHOD); } public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) { @@ -321,6 +322,39 @@ public class HttpTestUtils { return makeCacheEntry(now, now); } + public static HttpCacheEntry makeCacheEntryWithNoRequestMethodOrEntity(final Header[] headers) { + final Date now = new Date(); + return new HttpCacheEntry(now, now, makeStatusLine(), headers, null, null, null); + } + + public static HttpCacheEntry makeCacheEntryWithNoRequestMethod(final Header[] headers) { + final Date now = new Date(); + return new HttpCacheEntry(now, now, makeStatusLine(), headers, new HeapResource(getRandomBytes(128)), null, null); + } + + public static HttpCacheEntry make204CacheEntryWithNoRequestMethod(final Header[] headers) { + final Date now = new Date(); + return new HttpCacheEntry(now, now, make204StatusLine(), headers, null, null, HeaderConstants.HEAD_METHOD); + } + + public static HttpCacheEntry makeHeadCacheEntry(final Header[] headers) { + final Date now = new Date(); + return new HttpCacheEntry(now, now, makeStatusLine(), headers, null, null, HeaderConstants.HEAD_METHOD); + } + + public static HttpCacheEntry makeHeadCacheEntryWithNoRequestMethod(final Header[] headers) { + final Date now = new Date(); + return new HttpCacheEntry(now, now, makeStatusLine(), headers, null, null, null); + } + + public static StatusLine makeStatusLine() { + return new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); + } + + public static StatusLine make204StatusLine() { + return new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_NO_CONTENT, "OK"); + } + public static HttpResponse make200Response() { final HttpResponse out = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); out.setHeader("Date", DateUtils.formatDate(new Date())); @@ -356,8 +390,19 @@ public class HttpTestUtils { return new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1); } + public static HttpRequest makeDefaultHEADRequest() { + return new BasicHttpRequest("HEAD","/",HttpVersion.HTTP_1_1); + } + public static HttpResponse make500Response() { return new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error"); } + + public static Map makeDefaultVariantMap(final String key, final String value) { + final Map variants = new HashMap(); + variants.put(key, value); + + return variants; + } } 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 index 6652c16ae..8549ab17b 100644 --- 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 @@ -262,6 +262,15 @@ public class TestBasicHttpCache { assertFalse(impl.isIncompleteResponse(resp, resource)); } + @Test + public void testNullResourcesAreComplete() + throws Exception { + final HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); + resp.setHeader("Content-Length","256"); + + assertFalse(impl.isIncompleteResponse(resp, null)); + } + @Test public void testIncompleteResponseErrorProvidesPlainTextErrorMessage() throws Exception { diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java index a745010d7..fb7a513cd 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java @@ -80,7 +80,7 @@ public class TestCacheEntryUpdater { throws IOException { entry = HttpTestUtils.makeCacheEntry(); final HttpCacheEntry newEntry = impl.updateCacheEntry(null, entry, - requestDate, responseDate, response); + requestDate, responseDate, response, false); assertNotSame(newEntry, entry); } @@ -93,7 +93,7 @@ public class TestCacheEntryUpdater { response.setHeaders(new Header[]{}); final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry, - new Date(), new Date(), response); + new Date(), new Date(), response, false); final Header[] updatedHeaders = updatedEntry.getAllHeaders(); @@ -117,7 +117,7 @@ public class TestCacheEntryUpdater { new BasicHeader("Cache-Control", "public")}); final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry, - new Date(), new Date(), response); + new Date(), new Date(), response, false); final Header[] updatedHeaders = updatedEntry.getAllHeaders(); @@ -141,7 +141,7 @@ public class TestCacheEntryUpdater { new BasicHeader("Cache-Control", "public"),}); final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry, - new Date(), new Date(), response); + new Date(), new Date(), response, false); final Header[] updatedHeaders = updatedEntry.getAllHeaders(); assertEquals(4, updatedHeaders.length); @@ -163,7 +163,7 @@ public class TestCacheEntryUpdater { response.setHeader("Date", DateUtils.formatDate(tenSecondsAgo)); response.setHeader("ETag", "\"old-etag\""); final HttpCacheEntry result = impl.updateCacheEntry("A", entry, new Date(), - new Date(), response); + new Date(), response, false); assertEquals(2, result.getAllHeaders().length); headersContain(result.getAllHeaders(), "Date", DateUtils.formatDate(oneSecondAgo)); headersContain(result.getAllHeaders(), "ETag", "\"new-etag\""); @@ -174,7 +174,7 @@ public class TestCacheEntryUpdater { throws IOException { entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo); final HttpCacheEntry updated = impl.updateCacheEntry(null, entry, - twoSecondsAgo, oneSecondAgo, response); + twoSecondsAgo, oneSecondAgo, response, false); assertEquals(twoSecondsAgo, updated.getRequestDate()); assertEquals(oneSecondAgo, updated.getResponseDate()); @@ -191,7 +191,7 @@ public class TestCacheEntryUpdater { response.setHeader("ETag", "\"new\""); response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo)); final HttpCacheEntry updated = impl.updateCacheEntry(null, entry, - twoSecondsAgo, oneSecondAgo, response); + twoSecondsAgo, oneSecondAgo, response, false); assertEquals(0, updated.getHeaders("Warning").length); } @@ -206,7 +206,7 @@ public class TestCacheEntryUpdater { response.setHeader("ETag", "\"new\""); response.setHeader("Date", DateUtils.formatDate(twoSecondsAgo)); final HttpCacheEntry updated = impl.updateCacheEntry(null, entry, - twoSecondsAgo, oneSecondAgo, response); + twoSecondsAgo, oneSecondAgo, response, false); assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue()); } @@ -221,7 +221,7 @@ public class TestCacheEntryUpdater { response.setHeader("ETag", "\"new\""); response.setHeader("Date", "bad-date"); final HttpCacheEntry updated = impl.updateCacheEntry(null, entry, - twoSecondsAgo, oneSecondAgo, response); + twoSecondsAgo, oneSecondAgo, response, false); assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue()); } @@ -233,7 +233,7 @@ public class TestCacheEntryUpdater { HttpStatus.SC_OK, "OK"); try { impl.updateCacheEntry("A", entry, new Date(), new Date(), - response); + response, false); fail("should have thrown exception"); } catch (final IllegalArgumentException expected) { } 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 9e9994fc7..b595b4c8a 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 @@ -84,7 +84,7 @@ public class TestCacheInvalidator { request = HttpTestUtils.makeDefaultRequest(); response = HttpTestUtils.make200Response(); - impl = new CacheInvalidator(cacheKeyGenerator, mockStorage); + impl = new CacheInvalidator(cacheKeyGenerator, mockStorage, false); } private void replayMocks() { @@ -221,6 +221,81 @@ public class TestCacheInvalidator { verifyMocks(); } + @Test + public void testInvalidatesHEADCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception { + impl = new CacheInvalidator(cacheKeyGenerator, mockStorage, true); + final String theURI = "http://foo.example.com:80/"; + request = new BasicHttpRequest("GET", theURI,HTTP_1_1); + + cacheEntryisForMethod("HEAD"); + cacheEntryHasVariantMap(new HashMap()); + cacheReturnsEntryForUri(theURI); + entryIsRemoved(theURI); + + replayMocks(); + impl.flushInvalidatedCacheEntries(host, request); + verifyMocks(); + } + + @Test + public void testInvalidatesVariantHEADCacheEntriesIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception { + impl = new CacheInvalidator(cacheKeyGenerator, mockStorage, true); + final String theURI = "http://foo.example.com:80/"; + request = new BasicHttpRequest("GET", theURI,HTTP_1_1); + final String theVariantKey = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}"; + final String theVariantURI = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}http://foo.example.com:80/"; + final Map variants = HttpTestUtils.makeDefaultVariantMap(theVariantKey, theVariantURI); + + cacheEntryisForMethod("HEAD"); + cacheEntryHasVariantMap(variants); + cacheReturnsEntryForUri(theURI); + entryIsRemoved(theURI); + entryIsRemoved(theVariantURI); + + replayMocks(); + impl.flushInvalidatedCacheEntries(host, request); + verifyMocks(); + } + + @Test + public void testDoesNotInvalidateHEADCacheEntryIfHEADResponseCachingIsNotEnabled() throws Exception { + final String theURI = "http://foo.example.com:80/"; + request = new BasicHttpRequest("HEAD", theURI,HTTP_1_1); + + cacheReturnsEntryForUri(theURI); + + replayMocks(); + impl.flushInvalidatedCacheEntries(host, request); + verifyMocks(); + } + + @Test + public void testDoesNotInvalidateHEADCacheEntryIfSubsequentHEADRequestsAreMadeToTheSameURI() throws Exception { + impl = new CacheInvalidator(cacheKeyGenerator, mockStorage, true); + final String theURI = "http://foo.example.com:80/"; + request = new BasicHttpRequest("HEAD", theURI,HTTP_1_1); + + cacheReturnsEntryForUri(theURI); + + replayMocks(); + impl.flushInvalidatedCacheEntries(host, request); + verifyMocks(); + } + + @Test + public void testDoesNotInvalidateGETCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception { + impl = new CacheInvalidator(cacheKeyGenerator, mockStorage, true); + final String theURI = "http://foo.example.com:80/"; + request = new BasicHttpRequest("GET", theURI,HTTP_1_1); + + cacheEntryisForMethod("GET"); + cacheReturnsEntryForUri(theURI); + + replayMocks(); + impl.flushInvalidatedCacheEntries(host, request); + verifyMocks(); + } + @Test public void testDoesNotInvalidateRequestsWithClientCacheControlHeaders() throws Exception { request = new BasicHttpRequest("GET","/",HTTP_1_1); @@ -244,9 +319,7 @@ public class TestCacheInvalidator { request = new BasicHttpRequest("POST","/",HTTP_1_1); final String theUri = "http://foo.example.com:80/"; final String variantUri = "theVariantURI"; - - final Map mapOfURIs = new HashMap(); - mapOfURIs.put(variantUri,variantUri); + final Map mapOfURIs = HttpTestUtils.makeDefaultVariantMap(variantUri, variantUri); cacheReturnsEntryForUri(theUri); cacheEntryHasVariantMap(mapOfURIs); @@ -651,4 +724,8 @@ public class TestCacheInvalidator { mockStorage.removeEntry(theUri); } + private void cacheEntryisForMethod(final String httpMethod) { + expect(mockEntry.getRequestMethod()).andReturn(httpMethod); + } + } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java index 5357276af..5e8e5e1d3 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java @@ -354,6 +354,14 @@ public class TestCacheValidityPolicy { assertFalse(impl.contentLengthHeaderMatchesActualLength(entry)); } + @Test + public void testNullResourceInvalidatesEntry() { + final int contentLength = 128; + final Header[] headers = {new BasicHeader(HTTP.CONTENT_LEN, Integer.toString(contentLength))}; + final HttpCacheEntry entry = HttpTestUtils.makeHeadCacheEntry(headers); + assertFalse(impl.contentLengthHeaderMatchesActualLength(entry)); + } + @Test public void testMalformedContentLengthReturnsNegativeOne() { final Header[] headers = new Header[] { new BasicHeader("Content-Length", "asdf") }; diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheableRequestPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheableRequestPolicy.java index 927a7cd45..08edf531d 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheableRequestPolicy.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheableRequestPolicy.java @@ -35,6 +35,8 @@ public class TestCacheableRequestPolicy { private CacheableRequestPolicy policy; + private final boolean allowHeadResponseCaching = true; + @Before public void setUp() throws Exception { policy = new CacheableRequestPolicy(); @@ -44,8 +46,7 @@ public class TestCacheableRequestPolicy { public void testIsGetServableFromCache() { final BasicHttpRequest request = new BasicHttpRequest("GET", "someUri"); - Assert.assertTrue(policy.isServableFromCache(request)); - + Assert.assertTrue(policy.isServableFromCache(request, !allowHeadResponseCaching)); } @Test @@ -53,19 +54,19 @@ public class TestCacheableRequestPolicy { BasicHttpRequest request = new BasicHttpRequest("GET", "someUri"); request.addHeader("Cache-Control", "no-cache"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); request = new BasicHttpRequest("GET", "someUri"); request.addHeader("Cache-Control", "no-store"); request.addHeader("Cache-Control", "max-age=20"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); request = new BasicHttpRequest("GET", "someUri"); request.addHeader("Cache-Control", "public"); request.addHeader("Cache-Control", "no-store, max-age=20"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); } @Test @@ -73,25 +74,71 @@ public class TestCacheableRequestPolicy { BasicHttpRequest request = new BasicHttpRequest("GET", "someUri"); request.addHeader("Pragma", "no-cache"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); request = new BasicHttpRequest("GET", "someUri"); request.addHeader("Pragma", "value1"); request.addHeader("Pragma", "value2"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); + } + + @Test + public void testIsHeadServableFromCache() { + BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri"); + + Assert.assertTrue(policy.isServableFromCache(request, allowHeadResponseCaching)); + + request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Cache-Control", "public"); + request.addHeader("Cache-Control", "max-age=20"); + + Assert.assertTrue(policy.isServableFromCache(request, allowHeadResponseCaching)); + } + + @Test + public void testIsHeadWithCacheControlServableFromCache() { + BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Cache-Control", "no-cache"); + + Assert.assertFalse(policy.isServableFromCache(request, allowHeadResponseCaching)); + + request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Cache-Control", "no-store"); + request.addHeader("Cache-Control", "max-age=20"); + + Assert.assertFalse(policy.isServableFromCache(request, allowHeadResponseCaching)); + + request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Cache-Control", "public"); + request.addHeader("Cache-Control", "no-store, max-age=20"); + + Assert.assertFalse(policy.isServableFromCache(request, allowHeadResponseCaching)); + } + + @Test + public void testIsHeadWithPragmaServableFromCache() { + BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Pragma", "no-cache"); + + Assert.assertFalse(policy.isServableFromCache(request, allowHeadResponseCaching)); + + request = new BasicHttpRequest("HEAD", "someUri"); + request.addHeader("Pragma", "value1"); + request.addHeader("Pragma", "value2"); + + Assert.assertFalse(policy.isServableFromCache(request, allowHeadResponseCaching)); } @Test public void testIsArbitraryMethodServableFromCache() { + BasicHttpRequest request = new BasicHttpRequest("TRACE", "someUri"); - BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri"); - - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); request = new BasicHttpRequest("get", "someUri"); - Assert.assertFalse(policy.isServableFromCache(request)); + Assert.assertFalse(policy.isServableFromCache(request, !allowHeadResponseCaching)); } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedHttpResponseGenerator.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedHttpResponseGenerator.java index d3b8cf56c..1f5556a1d 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedHttpResponseGenerator.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedHttpResponseGenerator.java @@ -27,10 +27,12 @@ package org.apache.http.impl.client.cache; import java.util.Date; +import java.util.HashMap; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.cache.HttpCacheEntry; +import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.client.utils.DateUtils; import org.apache.http.message.BasicHeader; import org.easymock.classextension.EasyMock; @@ -42,6 +44,7 @@ import org.junit.Test; public class TestCachedHttpResponseGenerator { private HttpCacheEntry entry; + private HttpRequestWrapper request; private CacheValidityPolicy mockValidityPolicy; private CachedHttpResponseGenerator impl; private Date now; @@ -49,15 +52,14 @@ public class TestCachedHttpResponseGenerator { @Before public void setUp() { now = new Date(); - final Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); final Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L); - final Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L); final Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L); final Header[] hdrs = { new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo)), new BasicHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)), new BasicHeader("Content-Length", "150") }; - entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, sixSecondsAgo, hdrs); + entry = HttpTestUtils.makeCacheEntry(new HashMap()); + request = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultRequest()); mockValidityPolicy = EasyMock.createNiceMock(CacheValidityPolicy.class); impl = new CachedHttpResponseGenerator(mockValidityPolicy); } @@ -71,7 +73,7 @@ public class TestCachedHttpResponseGenerator { final byte[] buf = new byte[] { 1, 2, 3, 4, 5 }; final HttpCacheEntry entry1 = HttpTestUtils.makeCacheEntry(buf); - final HttpResponse response = impl.generateResponse(entry1); + final HttpResponse response = impl.generateResponse(request, entry1); final Header length = response.getFirstHeader("Content-Length"); Assert.assertNotNull("Content-Length Header is missing", length); @@ -87,7 +89,7 @@ public class TestCachedHttpResponseGenerator { final byte[] buf = new byte[] { 1, 2, 3, 4, 5 }; final HttpCacheEntry entry1 = HttpTestUtils.makeCacheEntry(hdrs, buf); - final HttpResponse response = impl.generateResponse(entry1); + final HttpResponse response = impl.generateResponse(request, entry1); final Header length = response.getFirstHeader("Content-Length"); @@ -96,7 +98,7 @@ public class TestCachedHttpResponseGenerator { @Test public void testResponseMatchesCacheEntry() { - final HttpResponse response = impl.generateResponse(entry); + final HttpResponse response = impl.generateResponse(request, entry); Assert.assertTrue(response.containsHeader("Content-Length")); @@ -107,7 +109,7 @@ public class TestCachedHttpResponseGenerator { @Test public void testResponseStatusCodeMatchesCacheEntry() { - final HttpResponse response = impl.generateResponse(entry); + final HttpResponse response = impl.generateResponse(request, entry); Assert.assertEquals(entry.getStatusCode(), response.getStatusLine().getStatusCode()); } @@ -117,7 +119,7 @@ public class TestCachedHttpResponseGenerator { currentAge(10L); replayMocks(); - final HttpResponse response = impl.generateResponse(entry); + final HttpResponse response = impl.generateResponse(request, entry); final Header ageHdr = response.getFirstHeader("Age"); Assert.assertNotNull(ageHdr); @@ -129,7 +131,7 @@ public class TestCachedHttpResponseGenerator { currentAge(0L); replayMocks(); - final HttpResponse response = impl.generateResponse(entry); + final HttpResponse response = impl.generateResponse(request, entry); final Header ageHdr = response.getFirstHeader("Age"); Assert.assertNull(ageHdr); @@ -140,7 +142,7 @@ public class TestCachedHttpResponseGenerator { currentAge(CacheValidityPolicy.MAX_AGE + 1L); replayMocks(); - final HttpResponse response = impl.generateResponse(entry); + final HttpResponse response = impl.generateResponse(request, entry); final Header ageHdr = response.getFirstHeader("Age"); Assert.assertNotNull(ageHdr); @@ -153,4 +155,19 @@ public class TestCachedHttpResponseGenerator { EasyMock.isA(Date.class))).andReturn(sec); } + @Test + public void testResponseContainsEntityToServeGETRequestIfEntryContainsResource() throws Exception { + final HttpResponse response = impl.generateResponse(request, entry); + + Assert.assertNotNull(response.getEntity()); + } + + @Test + public void testResponseDoesNotContainEntityToServeHEADRequestIfEntryContainsResource() throws Exception { + final HttpRequestWrapper headRequest = HttpRequestWrapper.wrap(HttpTestUtils.makeDefaultHEADRequest()); + final HttpResponse response = impl.generateResponse(headRequest, entry); + + Assert.assertNull(response.getEntity()); + } + } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java index 073502613..df6f6b2b3 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java @@ -261,4 +261,81 @@ public class TestCachedResponseSuitabilityChecker { Assert.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now)); } + + @Test + public void testSuitableIfRequestMethodisHEAD() { + final HttpRequest headRequest = new BasicHttpRequest("HEAD", "/foo", HttpVersion.HTTP_1_1); + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600"), + new BasicHeader("Content-Length","128") + }; + entry = getEntry(headers); + + Assert.assertTrue(impl.canCachedResponseBeUsed(host, headRequest, entry, now)); + } + + @Test + public void testNotSuitableIfRequestMethodIsGETAndEntryResourceIsNull() { + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600"), + new BasicHeader("Content-Length","128") + }; + entry = HttpTestUtils.makeHeadCacheEntry(headers); + + Assert.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now)); + } + + @Test + public void testNotSuitableForGETIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethodOrEntity() { + impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().setAllowHeadResponseCaching(true).build()); + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600"), + new BasicHeader("Content-Length","128") + }; + entry = HttpTestUtils.makeCacheEntryWithNoRequestMethodOrEntity(headers); + + Assert.assertFalse(impl.canCachedResponseBeUsed(host, request, entry, now)); + } + + @Test + public void testSuitableForGETIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethodButContainsEntity() { + impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().setAllowHeadResponseCaching(true).build()); + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600"), + new BasicHeader("Content-Length","128") + }; + entry = HttpTestUtils.makeCacheEntryWithNoRequestMethod(headers); + + Assert.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now)); + } + + @Test + public void testSuitableForGETIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethodButContains204Response() { + impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().setAllowHeadResponseCaching(true).build()); + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600") + }; + entry = HttpTestUtils.make204CacheEntryWithNoRequestMethod(headers); + + Assert.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now)); + } + + @Test + public void testSuitableForHEADIfHeadResponseCachingEnabledAndEntryDoesNotSpecifyARequestMethod() { + final HttpRequest headRequest = new BasicHttpRequest("HEAD", "/foo", HttpVersion.HTTP_1_1); + impl = new CachedResponseSuitabilityChecker(CacheConfig.custom().setAllowHeadResponseCaching(true).build()); + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)), + new BasicHeader("Cache-Control", "max-age=3600"), + new BasicHeader("Content-Length","128") + }; + entry = HttpTestUtils.makeHeadCacheEntryWithNoRequestMethod(headers); + + Assert.assertTrue(impl.canCachedResponseBeUsed(host, headRequest, entry, now)); + } } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExecChain.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExecChain.java index aaa605feb..82f750ec2 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExecChain.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingExecChain.java @@ -27,6 +27,7 @@ package org.apache.http.impl.client.cache; import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.anyBoolean; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; @@ -1764,7 +1765,7 @@ public abstract class TestCachingExecChain { } protected void requestPolicyAllowsCaching(final boolean allow) { - expect(mockRequestPolicy.isServableFromCache((HttpRequest) anyObject())).andReturn(allow); + expect(mockRequestPolicy.isServableFromCache((HttpRequest) anyObject(), anyBoolean())).andReturn(allow); } protected void cacheEntrySuitable(final boolean suitable) { @@ -1781,8 +1782,9 @@ public abstract class TestCachingExecChain { } protected void responseIsGeneratedFromCache() { - expect(mockResponseGenerator.generateResponse((HttpCacheEntry) anyObject())).andReturn( - mockCachedResponse); + expect( + mockResponseGenerator.generateResponse((HttpRequestWrapper) anyObject(), (HttpCacheEntry) anyObject())) + .andReturn(mockCachedResponse); } } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java index 4e8abed8c..188f08c43 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java @@ -42,6 +42,7 @@ import org.apache.commons.codec.binary.Base64; import org.apache.http.Header; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; +import org.apache.http.client.cache.HeaderConstants; import org.apache.http.client.cache.HttpCacheEntry; import org.apache.http.client.cache.HttpCacheEntrySerializer; import org.apache.http.client.cache.Resource; @@ -93,7 +94,7 @@ public class TestHttpCacheEntrySerializers { variantMap.put("test variant 2","true"); final HttpCacheEntry cacheEntry = new HttpCacheEntry(new Date(), new Date(), slObj, headers, new HeapResource(Base64.decodeBase64(body - .getBytes(UTF8))), variantMap); + .getBytes(UTF8))), variantMap, HeaderConstants.GET_METHOD); return cacheEntry; } diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java index 5734053a9..4f0b68670 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java @@ -62,7 +62,7 @@ public class TestResponseCachingPolicy { sixSecondsAgo = new Date(now.getTime() - 6 * 1000L); tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L); - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request = new BasicHttpRequest("GET","/",HTTP_1_1); response = new BasicHttpResponse( new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); @@ -75,6 +75,19 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadCacheableIfHeadResponseCachingIsEnabled() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + Assert.assertTrue(policy.isResponseCacheable("HEAD", response)); + } + + @Test + public void testHeadIsNotCacheableIfHeadResponseCachingIsDisabled() { + request = new BasicHttpRequest("HEAD","/",HTTP_1_1); + policy = new ResponseCachingPolicy(0, true, false, false, false); + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testResponsesToRequestsWithAuthorizationHeadersAreNotCacheableBySharedCache() { request = new BasicHttpRequest("GET","/",HTTP_1_1); @@ -84,7 +97,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesToRequestsWithAuthorizationHeadersAreCacheableByNonSharedCache() { - policy = new ResponseCachingPolicy(0, false, false, false); + policy = new ResponseCachingPolicy(0, false, false, false, false); request = new BasicHttpRequest("GET","/",HTTP_1_1); request.setHeader("Authorization","Basic dXNlcjpwYXNzd2Q="); Assert.assertTrue(policy.isResponseCacheable(request,response)); @@ -136,7 +149,7 @@ public class TestResponseCachingPolicy { @Test public void test206ResponseCodeIsNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setStatusCode(HttpStatus.SC_PARTIAL_CONTENT); @@ -180,7 +193,7 @@ public class TestResponseCachingPolicy { @Test public void testPlain303ResponseCodeIsNotCacheableEvenIf303CachingEnabled() { - policy = new ResponseCachingPolicy(0, true, false, true); + policy = new ResponseCachingPolicy(0, true, false, true, false); response.setStatusCode(HttpStatus.SC_SEE_OTHER); response.removeHeaders("Expires"); response.removeHeaders("Cache-Control"); @@ -254,7 +267,7 @@ public class TestResponseCachingPolicy { @Test public void test200ResponseWithPrivateCacheControlIsCacheableByNonSharedCache() { - policy = new ResponseCachingPolicy(0, false, false, false); + policy = new ResponseCachingPolicy(0, false, false, false, false); response.setStatusCode(HttpStatus.SC_OK); response.setHeader("Cache-Control", "private"); Assert.assertTrue(policy.isResponseCacheable("GET", response)); @@ -267,6 +280,13 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoCacheCacheable() { + response.addHeader("Cache-Control", "no-cache"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithNoStoreCacheable() { response.addHeader("Cache-Control", "no-store"); @@ -274,6 +294,13 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoStoreCacheable() { + response.addHeader("Cache-Control", "no-store"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithNoStoreEmbeddedInListCacheable() { response.addHeader("Cache-Control", "public, no-store"); @@ -281,6 +308,13 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoStoreEmbeddedInListCacheable() { + response.addHeader("Cache-Control", "public, no-store"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithNoCacheEmbeddedInListCacheable() { response.addHeader("Cache-Control", "public, no-cache"); @@ -288,6 +322,13 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoCacheEmbeddedInListCacheable() { + response.addHeader("Cache-Control", "public, no-cache"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithNoCacheEmbeddedInListAfterFirstHeaderCacheable() { response.addHeader("Cache-Control", "max-age=20"); @@ -296,6 +337,14 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoCacheEmbeddedInListAfterFirstHeaderCacheable() { + response.addHeader("Cache-Control", "max-age=20"); + response.addHeader("Cache-Control", "public, no-cache"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithNoStoreEmbeddedInListAfterFirstHeaderCacheable() { response.addHeader("Cache-Control", "max-age=20"); @@ -304,6 +353,14 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithNoStoreEmbeddedInListAfterFirstHeaderCacheable() { + response.addHeader("Cache-Control", "max-age=20"); + response.addHeader("Cache-Control", "public, no-store"); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithAnyCacheControlCacheable() { response.addHeader("Cache-Control", "max=10"); @@ -319,6 +376,22 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithAnyCacheControlCacheable() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + response.addHeader("Cache-Control", "max=10"); + + Assert.assertTrue(policy.isResponseCacheable("HEAD", response)); + + response = new BasicHttpResponse( + new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, "")); + response.setHeader("Date", DateUtils.formatDate(new Date())); + response.addHeader("Cache-Control", "no-transform"); + response.setHeader("Content-Length", "0"); + + Assert.assertTrue(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsGetWithout200Cacheable() { HttpResponse response404 = new BasicHttpResponse(new BasicStatusLine(HTTP_1_1, @@ -332,6 +405,19 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable("GET", response404)); } + @Test + public void testIsHeadWithout200Cacheable() { + HttpResponse response404 = new BasicHttpResponse(new BasicStatusLine(HTTP_1_1, + HttpStatus.SC_NOT_FOUND, "")); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response404)); + + response404 = new BasicHttpResponse(new BasicStatusLine(HTTP_1_1, + HttpStatus.SC_GATEWAY_TIMEOUT, "")); + + Assert.assertFalse(policy.isResponseCacheable("HEAD", response404)); + } + @Test public void testVaryStarIsNotCacheable() { response.setHeader("Vary", "*"); @@ -340,7 +426,7 @@ public class TestResponseCachingPolicy { @Test public void testVaryStarIsNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -354,6 +440,13 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable("GET", response)); } + @Test + public void testIsHeadWithVaryHeaderCacheable() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + response.addHeader("Vary", "Accept-Encoding"); + Assert.assertTrue(policy.isResponseCacheable("HEAD", response)); + } + @Test public void testIsArbitraryMethodCacheable() { @@ -364,7 +457,7 @@ public class TestResponseCachingPolicy { @Test public void testIsArbitraryMethodCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request = new HttpOptions("http://foo.example.com/"); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); @@ -390,7 +483,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleAgeHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -408,7 +501,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleDateHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -425,7 +518,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMalformedDateHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -442,7 +535,7 @@ public class TestResponseCachingPolicy { @Test public void testResponsesWithMultipleExpiresHeadersAreNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -465,7 +558,7 @@ public class TestResponseCachingPolicy { @Test public void testResponseThatHasTooMuchContentIsNotCacheableUsingSharedPublicCache() { - policy = new ResponseCachingPolicy(0, true, false, false); + policy = new ResponseCachingPolicy(0, true, false, false, false); request.setHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); response.setHeader("Cache-Control", "public"); @@ -485,13 +578,26 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void testResponsesToHEADWithQueryParamsButNoExplicitCachingAreNotCacheable() { + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(0, true, true, false); + policy = new ResponseCachingPolicy(0, true, true, false, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void testResponsesToHEADWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() { + policy = new ResponseCachingPolicy(0, true, true, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -500,15 +606,33 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheable() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() { - policy = new ResponseCachingPolicy(0, true, true, false); + policy = new ResponseCachingPolicy(0, true, true, false, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatDate(now)); response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void testResponsesToHEADWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() { + policy = new ResponseCachingPolicy(0, true, true, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheable() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -516,14 +640,29 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheable() { + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() { - policy = new ResponseCachingPolicy(0, true, true, false); + policy = new ResponseCachingPolicy(0, true, true, false, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() { + policy = new ResponseCachingPolicy(0, true, true, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -533,9 +672,19 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(0, true, true, false); + policy = new ResponseCachingPolicy(0, true, true, false, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); response.setHeader("Date", DateUtils.formatDate(now)); @@ -543,6 +692,16 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() { + policy = new ResponseCachingPolicy(0, true, true, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheable() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -550,6 +709,13 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheable() { + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Via", "1.0 someproxy"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -559,9 +725,19 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + response.setHeader("Via", "1.0 someproxy"); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(0, true, true, true); + policy = new ResponseCachingPolicy(0, true, true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatDate(now)); response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); @@ -569,6 +745,16 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() { + policy = new ResponseCachingPolicy(0, true, true, true, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + response.setHeader("Via", "1.0 someproxy"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -578,9 +764,19 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + response.setHeader("Via", "HTTP/1.0 someproxy"); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() { - policy = new ResponseCachingPolicy(0, true, true, true); + policy = new ResponseCachingPolicy(0, true, true, true, false); request = new BasicHttpRequest("GET", "/foo?s=bar"); response.setHeader("Date", DateUtils.formatDate(now)); response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); @@ -588,6 +784,16 @@ public class TestResponseCachingPolicy { Assert.assertFalse(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() { + policy = new ResponseCachingPolicy(0, true, true, true, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + response.setHeader("Via", "HTTP/1.0 someproxy"); + Assert.assertFalse(policy.isResponseCacheable(request, response)); + } + @Test public void getsWithQueryParametersFrom1_1OriginsVia1_0ProxiesAreCacheableWithExpires() { request = new BasicHttpRequest("GET", "/foo?s=bar"); @@ -598,6 +804,17 @@ public class TestResponseCachingPolicy { Assert.assertTrue(policy.isResponseCacheable(request, response)); } + @Test + public void headsWithQueryParametersFrom1_1OriginsVia1_0ProxiesAreCacheableWithExpires() { + policy = new ResponseCachingPolicy(0, true, false, false, true); + request = new BasicHttpRequest("HEAD", "/foo?s=bar"); + response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK"); + response.setHeader("Date", DateUtils.formatDate(now)); + response.setHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)); + response.setHeader("Via", "1.1 someproxy"); + Assert.assertTrue(policy.isResponseCacheable(request, response)); + } + @Test public void notCacheableIfExpiresEqualsDateAndNoCacheControl() { response.setHeader("Date", DateUtils.formatDate(now)); @@ -635,7 +852,7 @@ public class TestResponseCachingPolicy { public void test303WithExplicitCachingHeadersWhenPermittedByConfig() { // HTTPbis working group says ok if explicitly indicated by // response headers - policy = new ResponseCachingPolicy(0, true, false, true); + policy = new ResponseCachingPolicy(0, true, false, true, false); response.setStatusCode(HttpStatus.SC_SEE_OTHER); response.setHeader("Date", DateUtils.formatDate(now)); response.setHeader("Cache-Control","max-age=300"); diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java index 24ca14f08..6460e85e8 100644 --- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java +++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java @@ -26,8 +26,8 @@ */ package org.apache.http.impl.client.cache; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; import java.io.ByteArrayInputStream; import java.util.Date; @@ -151,4 +151,5 @@ public class TestResponseProtocolCompliance { } assertTrue(closed.set || bais.read() == -1); } + }