Merge remote-tracking branch 'J-Cutajar/cache-head-responses' into trunk

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1637389 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Francois-Xavier Bonnet 2014-11-07 15:23:03 +00:00
parent 114a5bf4ab
commit 2883531e92
26 changed files with 772 additions and 145 deletions

View File

@ -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) {

View File

@ -62,6 +62,7 @@ public class HttpCacheEntry implements Serializable {
private final Resource resource;
private final Map<String,String> 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<String,String> variantMap) {
final Map<String,String> 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<String,String>(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<String,String>());
new HashMap<String,String>(), 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.

View File

@ -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();

View File

@ -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();
}

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -67,6 +67,8 @@ class ResponseCachingPolicy {
HttpStatus.SC_MOVED_PERMANENTLY,
HttpStatus.SC_GONE));
private final Set<Integer> 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<Integer>(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;
}

View File

@ -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<headers.length; i++) {
@ -258,7 +258,7 @@ public class TestHttpCacheEntry {
@Test
public void canConstructWithoutVariants() {
new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
new Header[]{}, mockResource, HeaderConstants.GET_METHOD);
}
@SuppressWarnings("unused")
@ -266,7 +266,7 @@ public class TestHttpCacheEntry {
public void canProvideVariantMap() {
new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource,
new HashMap<String,String>());
new HashMap<String,String>(), 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<String,String> 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<String,String> 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());
}
}

View File

@ -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<String,String> 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<String, String> makeDefaultVariantMap(final String key, final String value) {
final Map<String, String> variants = new HashMap<String, String>();
variants.put(key, value);
return variants;
}
}

View File

@ -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 {

View File

@ -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) {
}

View File

@ -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<String, String>());
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<String, String> 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<String,String> mapOfURIs = new HashMap<String,String>();
mapOfURIs.put(variantUri,variantUri);
final Map<String,String> 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);
}
}

View File

@ -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") };

View File

@ -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));
}

View File

@ -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<String, String>());
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());
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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");

View File

@ -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);
}
}