Removed dependency on classic (blocking) I/O APIs from HttpCache

This commit is contained in:
Oleg Kalnichevski 2017-10-02 11:13:42 +02:00
parent 73c67f221d
commit 20f4290d01
11 changed files with 338 additions and 419 deletions

View File

@ -27,7 +27,6 @@
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -43,16 +42,10 @@ import org.apache.hc.client5.http.cache.HttpCacheUpdateCallback;
import org.apache.hc.client5.http.cache.HttpCacheUpdateException; import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
import org.apache.hc.client5.http.cache.Resource; import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceFactory; import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.util.ByteArrayBuffer; import org.apache.hc.core5.util.ByteArrayBuffer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -65,9 +58,7 @@ class BasicHttpCache implements HttpCache {
private final CacheKeyGenerator uriExtractor; private final CacheKeyGenerator uriExtractor;
private final ResourceFactory resourceFactory; private final ResourceFactory resourceFactory;
private final long maxObjectSizeBytes;
private final CacheEntryUpdater cacheEntryUpdater; private final CacheEntryUpdater cacheEntryUpdater;
private final CachedHttpResponseGenerator responseGenerator;
private final HttpCacheInvalidator cacheInvalidator; private final HttpCacheInvalidator cacheInvalidator;
private final HttpCacheStorage storage; private final HttpCacheStorage storage;
@ -76,14 +67,11 @@ class BasicHttpCache implements HttpCache {
public BasicHttpCache( public BasicHttpCache(
final ResourceFactory resourceFactory, final ResourceFactory resourceFactory,
final HttpCacheStorage storage, final HttpCacheStorage storage,
final CacheConfig config,
final CacheKeyGenerator uriExtractor, final CacheKeyGenerator uriExtractor,
final HttpCacheInvalidator cacheInvalidator) { final HttpCacheInvalidator cacheInvalidator) {
this.resourceFactory = resourceFactory; this.resourceFactory = resourceFactory;
this.uriExtractor = uriExtractor; this.uriExtractor = uriExtractor;
this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory); this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory);
this.maxObjectSizeBytes = config.getMaxObjectSize();
this.responseGenerator = new CachedHttpResponseGenerator();
this.storage = storage; this.storage = storage;
this.cacheInvalidator = cacheInvalidator; this.cacheInvalidator = cacheInvalidator;
} }
@ -91,21 +79,16 @@ class BasicHttpCache implements HttpCache {
public BasicHttpCache( public BasicHttpCache(
final ResourceFactory resourceFactory, final ResourceFactory resourceFactory,
final HttpCacheStorage storage, final HttpCacheStorage storage,
final CacheConfig config,
final CacheKeyGenerator uriExtractor) { final CacheKeyGenerator uriExtractor) {
this( resourceFactory, storage, config, uriExtractor, this(resourceFactory, storage, uriExtractor, new CacheInvalidator(uriExtractor, storage));
new CacheInvalidator(uriExtractor, storage));
} }
public BasicHttpCache( public BasicHttpCache(final ResourceFactory resourceFactory, final HttpCacheStorage storage) {
final ResourceFactory resourceFactory, this( resourceFactory, storage, new CacheKeyGenerator());
final HttpCacheStorage storage,
final CacheConfig config) {
this( resourceFactory, storage, config, new CacheKeyGenerator());
} }
public BasicHttpCache(final CacheConfig config) { public BasicHttpCache(final CacheConfig config) {
this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config); this(new HeapResourceFactory(), new BasicHttpCacheStorage(config));
} }
public BasicHttpCache() { public BasicHttpCache() {
@ -194,42 +177,6 @@ class BasicHttpCache implements HttpCache {
} }
} }
boolean isIncompleteResponse(final HttpResponse resp, final Resource resource) {
final int status = resp.getCode();
if (status != HttpStatus.SC_OK
&& status != HttpStatus.SC_PARTIAL_CONTENT) {
return false;
}
final Header hdr = resp.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
if (hdr == null) {
return false;
}
final int contentLength;
try {
contentLength = Integer.parseInt(hdr.getValue());
} catch (final NumberFormatException nfe) {
return false;
}
if (resource == null) {
return false;
}
return (resource.length() < contentLength);
}
ClassicHttpResponse generateIncompleteResponseError(
final HttpResponse response, final Resource resource) {
final Integer contentLength = Integer.valueOf(response.getFirstHeader(HttpHeaders.CONTENT_LENGTH).getValue());
final ClassicHttpResponse error = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
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, resource.length());
final byte[] msgBytes = msg.getBytes();
error.setHeader("Content-Length", Integer.toString(msgBytes.length));
error.setEntity(new ByteArrayEntity(msgBytes));
return error;
}
HttpCacheEntry doGetUpdatedParentEntry( HttpCacheEntry doGetUpdatedParentEntry(
final String requestId, final String requestId,
final HttpCacheEntry existing, final HttpCacheEntry existing,
@ -284,37 +231,19 @@ class BasicHttpCache implements HttpCache {
return updatedEntry; return updatedEntry;
} }
@Override public HttpCacheEntry createCacheEntry(
public ClassicHttpResponse cacheAndReturnResponse(
final HttpHost host, final HttpHost host,
final HttpRequest request, final HttpRequest request,
final ClassicHttpResponse originResponse, final HttpResponse originResponse,
final ByteArrayBuffer content,
final Date requestSent, final Date requestSent,
final Date responseReceived) throws IOException { final Date responseReceived) throws IOException {
final Resource resource; final Resource resource;
final HttpEntity entity = originResponse.getEntity(); if (content != null) {
if (entity != null) { resource = resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length());
final ByteArrayBuffer buf = new ByteArrayBuffer(1024);
final InputStream instream = entity.getContent();
final byte[] tmp = new byte[2048];
long total = 0;
int l;
while ((l = instream.read(tmp)) != -1) {
buf.append(tmp, 0, l);
total += l;
if (total > maxObjectSizeBytes) {
originResponse.setEntity(new CombinedEntity(entity, buf));
return originResponse;
}
}
resource = resourceFactory.generate(request.getRequestUri(), buf.array(), 0, buf.length());
} else { } else {
resource = null; resource = null;
} }
originResponse.close();
if (isIncompleteResponse(originResponse, resource)) {
return generateIncompleteResponseError(originResponse, resource);
}
final HttpCacheEntry entry = new HttpCacheEntry( final HttpCacheEntry entry = new HttpCacheEntry(
requestSent, requestSent,
responseReceived, responseReceived,
@ -322,7 +251,7 @@ class BasicHttpCache implements HttpCache {
originResponse.getAllHeaders(), originResponse.getAllHeaders(),
resource); resource);
storeInCache(host, request, entry); storeInCache(host, request, entry);
return responseGenerator.generateResponse(request, entry); return entry;
} }
@Override @Override

View File

@ -27,6 +27,7 @@
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -49,8 +50,10 @@ import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
@ -60,12 +63,14 @@ import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.ByteArrayBuffer;
import org.apache.hc.core5.util.VersionInfo; import org.apache.hc.core5.util.VersionInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -155,7 +160,7 @@ public class CachingExec implements ExecChainHandler {
final ResourceFactory resourceFactory, final ResourceFactory resourceFactory,
final HttpCacheStorage storage, final HttpCacheStorage storage,
final CacheConfig config) { final CacheConfig config) {
this(new BasicHttpCache(resourceFactory, storage, config), config); this(new BasicHttpCache(resourceFactory, storage), config);
} }
public CachingExec() { public CachingExec() {
@ -423,7 +428,7 @@ public class CachingExec implements ExecChainHandler {
final ClassicHttpRequest request, final ClassicHttpRequest request,
final HttpContext context, final HttpContext context,
final HttpCacheEntry entry, final HttpCacheEntry entry,
final Date now) { final Date now) throws IOException {
if (staleResponseNotAllowed(request, entry, now)) { if (staleResponseNotAllowed(request, entry, now)) {
return generateGatewayTimeout(context); return generateGatewayTimeout(context);
} else { } else {
@ -723,48 +728,52 @@ public class CachingExec implements ExecChainHandler {
Date requestDate = getCurrentDate(); Date requestDate = getCurrentDate();
ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope); ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
Date responseDate = getCurrentDate(); try {
Date responseDate = getCurrentDate();
if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) { if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
backendResponse.close();
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
scope.originalRequest);
requestDate = getCurrentDate();
backendResponse = chain.proceed(unconditional, scope);
responseDate = getCurrentDate();
}
backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
final int statusCode = backendResponse.getCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
recordCacheUpdate(scope.clientContext);
}
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
target, request, cacheEntry,
backendResponse, requestDate, responseDate);
if (suitabilityChecker.isConditional(request)
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
return responseGenerator
.generateNotModifiedResponse(updatedEntry);
}
return responseGenerator.generateResponse(request, updatedEntry);
}
if (staleIfErrorAppliesTo(statusCode)
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
try {
final ClassicHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
return cachedResponse;
} finally {
backendResponse.close(); backendResponse.close();
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
scope.originalRequest);
requestDate = getCurrentDate();
backendResponse = chain.proceed(unconditional, scope);
responseDate = getCurrentDate();
} }
backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
final int statusCode = backendResponse.getCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
recordCacheUpdate(scope.clientContext);
}
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
target, request, cacheEntry,
backendResponse, requestDate, responseDate);
if (suitabilityChecker.isConditional(request)
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
return responseGenerator.generateNotModifiedResponse(updatedEntry);
}
return responseGenerator.generateResponse(request, updatedEntry);
}
if (staleIfErrorAppliesTo(statusCode)
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
try {
final ClassicHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
return cachedResponse;
} finally {
backendResponse.close();
}
}
return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();
throw ex;
} }
return handleBackendResponse(target, conditionalRequest, scope, requestDate, responseDate, backendResponse);
} }
private boolean staleIfErrorAppliesTo(final int statusCode) { private boolean staleIfErrorAppliesTo(final int statusCode) {
@ -774,6 +783,66 @@ public class CachingExec implements ExecChainHandler {
|| statusCode == HttpStatus.SC_GATEWAY_TIMEOUT; || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
} }
boolean isIncompleteResponse(final HttpResponse resp, final ByteArrayBuffer buffer) {
if (buffer == null) {
return false;
}
final int status = resp.getCode();
if (status != HttpStatus.SC_OK && status != HttpStatus.SC_PARTIAL_CONTENT) {
return false;
}
final Header hdr = resp.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
if (hdr == null) {
return false;
}
final int contentLength;
try {
contentLength = Integer.parseInt(hdr.getValue());
} catch (final NumberFormatException nfe) {
return false;
}
return buffer.length() < contentLength;
}
public ClassicHttpResponse cacheAndReturnResponse(
final HttpHost target,
final HttpRequest request,
final ClassicHttpResponse backendResponse,
final Date requestSent,
final Date responseReceived) throws IOException { final ByteArrayBuffer buf;
final HttpEntity entity = backendResponse.getEntity();
if (entity != null) {
buf = new ByteArrayBuffer(1024);
final InputStream instream = entity.getContent();
final byte[] tmp = new byte[2048];
long total = 0;
int l;
while ((l = instream.read(tmp)) != -1) {
buf.append(tmp, 0, l);
total += l;
if (total > cacheConfig.getMaxObjectSize()) {
backendResponse.setEntity(new CombinedEntity(entity, buf));
return backendResponse;
}
}
} else {
buf = null;
}
if (buf != null && isIncompleteResponse(backendResponse, buf)) {
final Header h = backendResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
final ClassicHttpResponse error = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
final String msg = String.format("Received incomplete response " +
"with Content-Length %s but actual body length %d",
h != null ? h.getValue() : null, buf.length());
error.setEntity(new StringEntity(msg, ContentType.TEXT_PLAIN));
backendResponse.close();
return error;
}
backendResponse.close();
final HttpCacheEntry entry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived);
return responseGenerator.generateResponse(request, entry);
}
ClassicHttpResponse handleBackendResponse( ClassicHttpResponse handleBackendResponse(
final HttpHost target, final HttpHost target,
final ClassicHttpRequest request, final ClassicHttpRequest request,
@ -789,7 +858,7 @@ public class CachingExec implements ExecChainHandler {
responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse); responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) { if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
storeRequestIfModifiedSinceFor304Response(request, backendResponse); storeRequestIfModifiedSinceFor304Response(request, backendResponse);
return responseCache.cacheAndReturnResponse(target, request, backendResponse, requestDate, responseDate); return cacheAndReturnResponse(target, request, backendResponse, requestDate, responseDate);
} }
if (!cacheable) { if (!cacheable) {
try { try {

View File

@ -147,7 +147,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
final CacheKeyGenerator uriExtractor = new CacheKeyGenerator(); final CacheKeyGenerator uriExtractor = new CacheKeyGenerator();
final HttpCache httpCache = new BasicHttpCache( final HttpCache httpCache = new BasicHttpCache(
resourceFactoryCopy, resourceFactoryCopy,
storageCopy, config, storageCopy,
uriExtractor, uriExtractor,
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new CacheInvalidator(uriExtractor, storageCopy)); this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new CacheInvalidator(uriExtractor, storageCopy));

View File

@ -31,10 +31,10 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.util.ByteArrayBuffer;
/** /**
* @since 4.1 * @since 4.1
@ -94,14 +94,19 @@ interface HttpCache {
* @param host * @param host
* @param request * @param request
* @param originResponse * @param originResponse
* @param content
* @param requestSent * @param requestSent
* @param responseReceived * @param responseReceived
* @return the {@link HttpResponse} * @return new {@link HttpCacheEntry}
* @throws IOException * @throws IOException
*/ */
ClassicHttpResponse cacheAndReturnResponse(HttpHost host, HttpCacheEntry createCacheEntry(
HttpRequest request, ClassicHttpResponse originResponse, HttpHost host,
Date requestSent, Date responseReceived) HttpRequest request,
HttpResponse originResponse,
ByteArrayBuffer content,
Date requestSent,
Date responseReceived)
throws IOException; throws IOException;
/** /**

View File

@ -52,6 +52,7 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.ByteArrayBuffer;
import org.apache.hc.core5.util.LangUtils; import org.apache.hc.core5.util.LangUtils;
import org.junit.Assert; import org.junit.Assert;
@ -243,6 +244,13 @@ public class HttpTestUtils {
return bytes; return bytes;
} }
public static ByteArrayBuffer getRandomBuffer(final int nbytes) {
final ByteArrayBuffer buf = new ByteArrayBuffer(nbytes);
buf.setLength(nbytes);
new Random().nextBytes(buf.array());
return buf;
}
/** Generates a response body with random content. /** Generates a response body with random content.
* @param nbytes length of the desired response body * @param nbytes length of the desired response body
* @return an {@link HttpEntity} * @return an {@link HttpEntity}

View File

@ -31,18 +31,13 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.hc.client5.http.cache.HeaderConstants; import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.classic.methods.HttpDelete; import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpHead; import org.apache.hc.client5.http.classic.methods.HttpHead;
@ -50,16 +45,14 @@ import org.apache.hc.client5.http.classic.methods.HttpOptions;
import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpTrace; import org.apache.hc.client5.http.classic.methods.HttpTrace;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.util.ByteArrayBuffer;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -72,7 +65,7 @@ public class TestBasicHttpCache {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
backing = new SimpleHttpCacheStorage(); backing = new SimpleHttpCacheStorage();
impl = new BasicHttpCache(new HeapResourceFactory(), backing, CacheConfig.DEFAULT); impl = new BasicHttpCache(new HeapResourceFactory(), backing);
} }
@Test @Test
@ -136,7 +129,7 @@ public class TestBasicHttpCache {
throws Exception { throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest req = new HttpPost("/foo"); final HttpRequest req = new HttpPost("/foo");
final ClassicHttpResponse resp = HttpTestUtils.make200Response(); final HttpResponse resp = HttpTestUtils.make200Response();
resp.setHeader("Content-Location", "/bar"); resp.setHeader("Content-Location", "/bar");
resp.setHeader(HeaderConstants.ETAG, "\"etag\""); resp.setHeader(HeaderConstants.ETAG, "\"etag\"");
final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar")); final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar"));
@ -158,7 +151,7 @@ public class TestBasicHttpCache {
throws Exception { throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest req = new HttpGet("/foo"); final HttpRequest req = new HttpGet("/foo");
final ClassicHttpResponse resp = HttpTestUtils.make200Response(); final HttpResponse resp = HttpTestUtils.make200Response();
resp.setHeader("Content-Location", "/bar"); resp.setHeader("Content-Location", "/bar");
final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar")); final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar"));
@ -188,127 +181,6 @@ public class TestBasicHttpCache {
assertNull(backing.map.get(key)); assertNull(backing.map.get(key));
} }
@Test
public void testRecognizesComplete200Response()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
final Resource resource = new HeapResource(bytes);
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testRecognizesComplete206Response()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","128");
resp.setHeader("Content-Range","bytes 0-127/255");
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testRecognizesIncomplete200Response()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
assertTrue(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testIgnoresIncompleteNon200Or206Responses()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_FORBIDDEN, "Forbidden");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testResponsesWithoutExplicitContentLengthAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testResponsesWithUnparseableContentLengthHeaderAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setHeader("Content-Length","foo");
resp.setEntity(new ByteArrayEntity(bytes));
assertFalse(impl.isIncompleteResponse(resp, resource));
}
@Test
public void testNullResourcesAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
resp.setHeader("Content-Length","256");
assertFalse(impl.isIncompleteResponse(resp, null));
}
@Test
public void testIncompleteResponseErrorProvidesPlainTextErrorMessage()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
final ClassicHttpResponse result = impl.generateIncompleteResponseError(resp, resource);
final Header ctype = result.getFirstHeader("Content-Type");
assertEquals("text/plain;charset=UTF-8", ctype.getValue());
}
@Test
public void testIncompleteResponseErrorProvidesNonEmptyErrorMessage()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
final Resource resource = new HeapResource(bytes);
resp.setEntity(new ByteArrayEntity(bytes));
resp.setHeader("Content-Length","256");
final ClassicHttpResponse result = impl.generateIncompleteResponseError(resp, resource);
final int clen = Integer.parseInt(result.getFirstHeader("Content-Length").getValue());
assertTrue(clen > 0);
final HttpEntity body = result.getEntity();
if (body.getContentLength() < 0) {
final InputStream is = body.getContent();
int bytes_read = 0;
while((is.read()) != -1) {
bytes_read++;
}
is.close();
assertEquals(clen, bytes_read);
} else {
assertTrue(body.getContentLength() == clen);
}
}
@Test @Test
public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception { public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
final String parentCacheKey = "parentCacheKey"; final String parentCacheKey = "parentCacheKey";
@ -340,50 +212,6 @@ public class TestBasicHttpCache {
assertSame(entry, backing.map.get(key)); assertSame(entry, backing.map.get(key));
} }
@Test
public void testTooLargeResponsesAreNotCached() throws Exception {
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final Date now = new Date();
final Date requestSent = new Date(now.getTime() - 3 * 1000L);
final Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
final Date responseReceived = new Date(now.getTime() - 1 * 1000L);
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES + 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
final ClassicHttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
assertEquals(0, backing.map.size());
assertSame(originResponse, result);
}
@Test
public void testSmallEnoughResponsesAreCached() throws Exception {
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final Date now = new Date();
final Date requestSent = new Date(now.getTime() - 3 * 1000L);
final Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
final Date responseReceived = new Date(now.getTime() - 1 * 1000L);
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
final ClassicHttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
assertEquals(1, backing.map.size());
assertTrue(backing.map.containsKey((new CacheKeyGenerator()).generateKey(host, request)));
assertTrue(HttpTestUtils.semanticallyTransparent(originResponse, result));
}
@Test @Test
public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception { public void testGetCacheEntryReturnsNullOnCacheMiss() throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
@ -410,20 +238,21 @@ public class TestBasicHttpCache {
@Test @Test
public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception { public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar"); final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip"); origRequest.setHeader("Accept-Encoding","gzip");
final ClassicHttpResponse origResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
origResponse.setEntity(HttpTestUtils.makeBody(128)); final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setHeader("Date", DateUtils.formatDate(new Date())); origResponse.setHeader("Date", DateUtils.formatDate(new Date()));
origResponse.setHeader("Cache-Control", "max-age=3600, public"); origResponse.setHeader("Cache-Control", "max-age=3600, public");
origResponse.setHeader("ETag", "\"etag\""); origResponse.setHeader("ETag", "\"etag\"");
origResponse.setHeader("Vary", "Accept-Encoding"); origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip"); origResponse.setHeader("Content-Encoding","gzip");
impl.cacheAndReturnResponse(host, origRequest, origResponse, new Date(), new Date()); impl.createCacheEntry(host, origRequest, origResponse, buf, new Date(), new Date());
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final HttpCacheEntry result = impl.getCacheEntry(host, request); final HttpCacheEntry result = impl.getCacheEntry(host, request);
assertNull(result); assertNull(result);
} }
@ -431,21 +260,22 @@ public class TestBasicHttpCache {
@Test @Test
public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception { public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
request.setHeader("Accept-Encoding","gzip");
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar"); final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip"); origRequest.setHeader("Accept-Encoding","gzip");
final ClassicHttpResponse origResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
origResponse.setEntity(HttpTestUtils.makeBody(128)); final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setHeader("Date", DateUtils.formatDate(new Date())); origResponse.setHeader("Date", DateUtils.formatDate(new Date()));
origResponse.setHeader("Cache-Control", "max-age=3600, public"); origResponse.setHeader("Cache-Control", "max-age=3600, public");
origResponse.setHeader("ETag", "\"etag\""); origResponse.setHeader("ETag", "\"etag\"");
origResponse.setHeader("Vary", "Accept-Encoding"); origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip"); origResponse.setHeader("Content-Encoding","gzip");
impl.cacheAndReturnResponse(host, origRequest, origResponse, new Date(), new Date()); impl.createCacheEntry(host, origRequest, origResponse, buf, new Date(), new Date());
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
request.setHeader("Accept-Encoding","gzip");
final HttpCacheEntry result = impl.getCacheEntry(host, request); final HttpCacheEntry result = impl.getCacheEntry(host, request);
assertNotNull(result); assertNotNull(result);
} }
@ -467,7 +297,7 @@ public class TestBasicHttpCache {
final HttpRequest req1 = new HttpGet("http://foo.example.com/bar"); final HttpRequest req1 = new HttpGet("http://foo.example.com/bar");
req1.setHeader("Accept-Encoding", "gzip"); req1.setHeader("Accept-Encoding", "gzip");
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(); final HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(new Date())); resp1.setHeader("Date", DateUtils.formatDate(new Date()));
resp1.setHeader("Cache-Control", "max-age=3600, public"); resp1.setHeader("Cache-Control", "max-age=3600, public");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
@ -478,7 +308,7 @@ public class TestBasicHttpCache {
final HttpRequest req2 = new HttpGet("http://foo.example.com/bar"); final HttpRequest req2 = new HttpGet("http://foo.example.com/bar");
req2.setHeader("Accept-Encoding", "identity"); req2.setHeader("Accept-Encoding", "identity");
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response(); final HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Date", DateUtils.formatDate(new Date())); resp2.setHeader("Date", DateUtils.formatDate(new Date()));
resp2.setHeader("Cache-Control", "max-age=3600, public"); resp2.setHeader("Cache-Control", "max-age=3600, public");
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
@ -486,8 +316,8 @@ public class TestBasicHttpCache {
resp2.setHeader("Content-Encoding","gzip"); resp2.setHeader("Content-Encoding","gzip");
resp2.setHeader("Vary", "Accept-Encoding"); resp2.setHeader("Vary", "Accept-Encoding");
impl.cacheAndReturnResponse(host, req1, resp1, new Date(), new Date()); impl.createCacheEntry(host, req1, resp1, null, new Date(), new Date());
impl.cacheAndReturnResponse(host, req2, resp2, new Date(), new Date()); impl.createCacheEntry(host, req2, resp2, null, new Date(), new Date());
final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, req1); final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, req1);
@ -496,55 +326,4 @@ public class TestBasicHttpCache {
} }
@Test
public void testOriginalResponseWithNoContentSizeHeaderIsReleased() throws Exception {
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final Date now = new Date();
final Date requestSent = new Date(now.getTime() - 3 * 1000L);
final Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
final Date responseReceived = new Date(now.getTime() - 1 * 1000L);
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final BasicHttpEntity entity = new BasicHttpEntity();
final ConsumableInputStream inputStream = new ConsumableInputStream(new ByteArrayInputStream(HttpTestUtils.getRandomBytes(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1)));
entity.setContent(inputStream);
originResponse.setEntity(entity);
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
final ClassicHttpResponse result = impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
IOUtils.consume(result.getEntity());
assertTrue(inputStream.wasClosed());
}
@Test
public void testEntryUpdate() throws Exception {
final HeapResourceFactory rf = new HeapResourceFactory();
impl = new BasicHttpCache(rf, backing, CacheConfig.DEFAULT);
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip");
final ClassicHttpResponse origResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setEntity(HttpTestUtils.makeBody(128));
origResponse.setHeader("Date", DateUtils.formatDate(new Date()));
origResponse.setHeader("Cache-Control", "max-age=3600, public");
origResponse.setHeader("ETag", "\"etag\"");
origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip");
final ClassicHttpResponse response = impl.cacheAndReturnResponse(
host, origRequest, origResponse, new Date(), new Date());
final HttpEntity entity = response.getEntity();
Assert.assertNotNull(entity);
IOUtils.copyAndClose(entity.getContent(), new ByteArrayOutputStream());
}
} }

View File

@ -290,7 +290,7 @@ public class TestCachingExec extends TestCachingExecChain {
eq(responseDate))) eq(responseDate)))
.andReturn(updatedEntry); .andReturn(updatedEntry);
expect(mockSuitabilityChecker.isConditional(request)).andReturn(false); expect(mockSuitabilityChecker.isConditional(request)).andReturn(false);
responseIsGeneratedFromCache(); responseIsGeneratedFromCache(HttpTestUtils.make200Response());
replayMocks(); replayMocks();
impl.revalidateCacheEntry(host, request, scope, mockExecChain, entry); impl.revalidateCacheEntry(host, request, scope, mockExecChain, entry);
@ -396,14 +396,11 @@ public class TestCachingExec extends TestCachingExecChain {
cacheEntrySuitable(true); cacheEntrySuitable(true);
cacheEntryValidatable(true); cacheEntryValidatable(true);
expect(mockResponseGenerator.generateResponse(isA(HttpRequest.class), isA(HttpCacheEntry.class))) responseIsGeneratedFromCache(HttpTestUtils.make200Response());
.andReturn(mockBackendResponse);
replayMocks(); replayMocks();
final HttpResponse result = impl.execute(request, scope, mockExecChain); impl.execute(request, scope, mockExecChain);
verifyMocks(); verifyMocks();
Assert.assertSame(mockBackendResponse, result);
} }
private IExpectationSetters<ClassicHttpResponse> implExpectsAnyRequestAndReturn( private IExpectationSetters<ClassicHttpResponse> implExpectsAnyRequestAndReturn(

View File

@ -31,10 +31,12 @@ import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.same;
import static org.easymock.classextension.EasyMock.createNiceMock; import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify; import static org.easymock.classextension.EasyMock.verify;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -54,7 +56,6 @@ import org.apache.hc.client5.http.cache.HttpCacheContext;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorage; import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.classic.ExecChain; import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.classic.ExecRuntime; import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpOptions; import org.apache.hc.client5.http.classic.methods.HttpOptions;
@ -77,6 +78,7 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.ByteArrayBuffer;
import org.easymock.Capture; import org.easymock.Capture;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.easymock.IExpectationSetters; import org.easymock.IExpectationSetters;
@ -89,7 +91,7 @@ import junit.framework.AssertionFailedError;
@SuppressWarnings("boxing") // test code @SuppressWarnings("boxing") // test code
public abstract class TestCachingExecChain { public abstract class TestCachingExecChain {
private ExecChainHandler impl; private CachingExec impl;
protected CacheValidityPolicy mockValidityPolicy; protected CacheValidityPolicy mockValidityPolicy;
protected CacheableRequestPolicy mockRequestPolicy; protected CacheableRequestPolicy mockRequestPolicy;
@ -103,7 +105,6 @@ public abstract class TestCachingExecChain {
protected CachedHttpResponseGenerator mockResponseGenerator; protected CachedHttpResponseGenerator mockResponseGenerator;
private HttpClientResponseHandler<Object> mockHandler; private HttpClientResponseHandler<Object> mockHandler;
private ClassicHttpRequest mockUriRequest; private ClassicHttpRequest mockUriRequest;
private ClassicHttpResponse mockCachedResponse;
protected ConditionalRequestBuilder mockConditionalRequestBuilder; protected ConditionalRequestBuilder mockConditionalRequestBuilder;
private HttpRequest mockConditionalRequest; private HttpRequest mockConditionalRequest;
protected ResponseProtocolCompliance mockResponseProtocolCompliance; protected ResponseProtocolCompliance mockResponseProtocolCompliance;
@ -131,7 +132,6 @@ public abstract class TestCachingExecChain {
mockUriRequest = createNiceMock(ClassicHttpRequest.class); mockUriRequest = createNiceMock(ClassicHttpRequest.class);
mockCacheEntry = createNiceMock(HttpCacheEntry.class); mockCacheEntry = createNiceMock(HttpCacheEntry.class);
mockResponseGenerator = createNiceMock(CachedHttpResponseGenerator.class); mockResponseGenerator = createNiceMock(CachedHttpResponseGenerator.class);
mockCachedResponse = createNiceMock(ClassicHttpResponse.class);
mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class); mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class);
mockConditionalRequest = createNiceMock(HttpRequest.class); mockConditionalRequest = createNiceMock(HttpRequest.class);
mockResponseProtocolCompliance = createNiceMock(ResponseProtocolCompliance.class); mockResponseProtocolCompliance = createNiceMock(ResponseProtocolCompliance.class);
@ -151,7 +151,7 @@ public abstract class TestCachingExecChain {
mockRequestProtocolCompliance, config, asyncValidator); mockRequestProtocolCompliance, config, asyncValidator);
} }
public abstract ExecChainHandler createCachingExecChain(HttpCache responseCache, CacheValidityPolicy validityPolicy, public abstract CachingExec createCachingExecChain(HttpCache responseCache, CacheValidityPolicy validityPolicy,
ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator, ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator,
CacheableRequestPolicy cacheableRequestPolicy, CacheableRequestPolicy cacheableRequestPolicy,
CachedResponseSuitabilityChecker suitabilityChecker, CachedResponseSuitabilityChecker suitabilityChecker,
@ -159,7 +159,7 @@ public abstract class TestCachingExecChain {
ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance, ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance,
CacheConfig config, AsynchronousValidator asynchRevalidator); CacheConfig config, AsynchronousValidator asynchRevalidator);
public abstract ExecChainHandler createCachingExecChain(HttpCache cache, CacheConfig config); public abstract CachingExec createCachingExecChain(HttpCache cache, CacheConfig config);
protected ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException { protected ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
return impl.execute(ClassicRequestCopier.INSTANCE.copy(request), new ExecChain.Scope( return impl.execute(ClassicRequestCopier.INSTANCE.copy(request), new ExecChain.Scope(
@ -187,7 +187,6 @@ public abstract class TestCachingExecChain {
replay(mockCache); replay(mockCache);
replay(mockHandler); replay(mockHandler);
replay(mockUriRequest); replay(mockUriRequest);
replay(mockCachedResponse);
replay(mockConditionalRequestBuilder); replay(mockConditionalRequestBuilder);
replay(mockConditionalRequest); replay(mockConditionalRequest);
replay(mockResponseProtocolCompliance); replay(mockResponseProtocolCompliance);
@ -206,7 +205,6 @@ public abstract class TestCachingExecChain {
verify(mockCache); verify(mockCache);
verify(mockHandler); verify(mockHandler);
verify(mockUriRequest); verify(mockUriRequest);
verify(mockCachedResponse);
verify(mockConditionalRequestBuilder); verify(mockConditionalRequestBuilder);
verify(mockConditionalRequest); verify(mockConditionalRequest);
verify(mockResponseProtocolCompliance); verify(mockResponseProtocolCompliance);
@ -314,22 +312,20 @@ public abstract class TestCachingExecChain {
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
getCacheEntryReturns(mockCacheEntry); getCacheEntryReturns(mockCacheEntry);
cacheEntrySuitable(true); cacheEntrySuitable(true);
responseIsGeneratedFromCache(); responseIsGeneratedFromCache(HttpTestUtils.make200Response());
requestIsFatallyNonCompliant(null); requestIsFatallyNonCompliant(null);
entryHasStaleness(0L); entryHasStaleness(0L);
replayMocks(); replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks(); verifyMocks();
Assert.assertSame(mockCachedResponse, result);
} }
@Test @Test
public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception { public void testNonCacheableResponseIsNotCachedAndIsReturnedAsIs() throws Exception {
final CacheConfig configDefault = CacheConfig.DEFAULT; final CacheConfig configDefault = CacheConfig.DEFAULT;
impl = createCachingExecChain(new BasicHttpCache(new HeapResourceFactory(), impl = createCachingExecChain(new BasicHttpCache(new HeapResourceFactory(),
mockStorage, configDefault), configDefault); mockStorage), configDefault);
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest(); final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(); final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
@ -354,7 +350,7 @@ public abstract class TestCachingExecChain {
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
cacheEntrySuitable(true); cacheEntrySuitable(true);
getCacheEntryReturns(mockCacheEntry); getCacheEntryReturns(mockCacheEntry);
responseIsGeneratedFromCache(); responseIsGeneratedFromCache(HttpTestUtils.make200Response());
entryHasStaleness(0L); entryHasStaleness(0L);
replayMocks(); replayMocks();
@ -1265,31 +1261,134 @@ public abstract class TestCachingExecChain {
} }
@Test @Test
public void testTreatsCacheIOExceptionsAsCacheMiss() throws Exception { public void testRecognizesComplete200Response()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
resp.setHeader("Content-Length","128");
assertFalse(impl.isIncompleteResponse(resp, buf));
}
impl = createCachingExecChain(mockCache, CacheConfig.DEFAULT); @Test
final ClassicHttpResponse resp = HttpTestUtils.make200Response(); public void testRecognizesComplete206Response()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
resp.setHeader("Content-Length","128");
resp.setHeader("Content-Range","bytes 0-127/255");
assertFalse(impl.isIncompleteResponse(resp, buf));
}
mockCache.flushInvalidatedCacheEntriesFor(host, request); @Test
expectLastCall().andThrow(new IOException()).anyTimes(); public void testRecognizesIncomplete200Response()
mockCache.flushInvalidatedCacheEntriesFor(isA(HttpHost.class), isA(HttpRequest.class), throws Exception {
isA(HttpResponse.class)); final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
expectLastCall().anyTimes(); final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
expect(mockCache.getCacheEntry(eq(host), isA(HttpRequest.class))).andThrow( resp.setHeader("Content-Length","256");
new IOException()).anyTimes();
expect(mockCache.getVariantCacheEntriesWithEtags(eq(host), isA(HttpRequest.class))) assertTrue(impl.isIncompleteResponse(resp, buf));
.andThrow(new IOException()).anyTimes(); }
expect(
mockCache.cacheAndReturnResponse(eq(host), isA(HttpRequest.class), @Test
isA(ClassicHttpResponse.class), isA(Date.class), isA(Date.class))) public void testIgnoresIncompleteNon200Or206Responses()
.andReturn(resp).anyTimes(); throws Exception {
expect( final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_FORBIDDEN, "Forbidden");
mockExecChain.proceed(isA(ClassicHttpRequest.class), isA(ExecChain.Scope.class))).andReturn(resp); final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
resp.setHeader("Content-Length","256");
assertFalse(impl.isIncompleteResponse(resp, buf));
}
@Test
public void testResponsesWithoutExplicitContentLengthAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
assertFalse(impl.isIncompleteResponse(resp, buf));
}
@Test
public void testResponsesWithUnparseableContentLengthHeaderAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
resp.setHeader("Content-Length","foo");
assertFalse(impl.isIncompleteResponse(resp, buf));
}
@Test
public void testNullResourcesAreComplete()
throws Exception {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
resp.setHeader("Content-Length","256");
assertFalse(impl.isIncompleteResponse(resp, null));
}
@Test
public void testTooLargeResponsesAreNotCached() throws Exception {
mockCache = EasyMock.createStrictMock(HttpCache.class);
impl = createCachingExecChain(mockCache, mockValidityPolicy,
mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
mockConditionalRequestBuilder, mockResponseProtocolCompliance,
mockRequestProtocolCompliance, config, asyncValidator);
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final Date now = new Date();
final Date requestSent = new Date(now.getTime() - 3 * 1000L);
final Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
final Date responseReceived = new Date(now.getTime() - 1 * 1000L);
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES + 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
replayMocks(); replayMocks();
final ClassicHttpResponse result = execute(request);
impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
verifyMocks();
}
@Test
public void testSmallEnoughResponsesAreCached() throws Exception {
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final Date now = new Date();
final Date requestSent = new Date(now.getTime() - 3 * 1000L);
final Date responseGenerated = new Date(now.getTime() - 2 * 1000L);
final Date responseReceived = new Date(now.getTime() - 1 * 1000L);
final ClassicHttpResponse originResponse = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
final ClassicHttpResponse response = HttpTestUtils.make200Response();
EasyMock.expect(mockCache.createCacheEntry(
eq(host),
same(request),
same(originResponse),
isA(ByteArrayBuffer.class),
eq(requestSent),
eq(responseReceived))).andReturn(httpCacheEntry).once();
EasyMock.expect(mockResponseGenerator.generateResponse(
same(request),
same(httpCacheEntry))).andReturn(response).once();
replayMocks();
impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
verifyMocks(); verifyMocks();
Assert.assertSame(resp, result);
} }
@Test @Test
@ -1332,14 +1431,12 @@ public abstract class TestCachingExecChain {
requestPolicyAllowsCaching(true); requestPolicyAllowsCaching(true);
getCacheEntryReturns(entry); getCacheEntryReturns(entry);
cacheEntrySuitable(true); cacheEntrySuitable(true);
responseIsGeneratedFromCache(); responseIsGeneratedFromCache(HttpTestUtils.make200Response());
entryHasStaleness(0); entryHasStaleness(0);
replayMocks(); replayMocks();
final ClassicHttpResponse resp = execute(request); final ClassicHttpResponse resp = execute(request);
verifyMocks(); verifyMocks();
Assert.assertSame(mockCachedResponse, resp);
} }
@Test @Test
@ -1669,10 +1766,11 @@ public abstract class TestCachingExecChain {
.andReturn(staleness); .andReturn(staleness);
} }
protected void responseIsGeneratedFromCache() { protected void responseIsGeneratedFromCache(final ClassicHttpResponse cachedResponse) throws IOException {
expect( expect(
mockResponseGenerator.generateResponse((ClassicHttpRequest) anyObject(), (HttpCacheEntry) anyObject())) mockResponseGenerator.generateResponse(
.andReturn(mockCachedResponse); (ClassicHttpRequest) anyObject(),
(HttpCacheEntry) anyObject())).andReturn(cachedResponse);
} }
protected void doesNotFlushCache() throws IOException { protected void doesNotFlushCache() throws IOException {

View File

@ -113,7 +113,7 @@ public class TestHttpCacheJiraNumber1147 {
isA(ClassicHttpRequest.class), isA(ClassicHttpRequest.class),
isA(ExecChain.Scope.class))).thenReturn(response); isA(ExecChain.Scope.class))).thenReturn(response);
final BasicHttpCache cache = new BasicHttpCache(resourceFactory, httpCacheStorage, cacheConfig); final BasicHttpCache cache = new BasicHttpCache(resourceFactory, httpCacheStorage);
final ExecChainHandler t = createCachingExecChain(cache, cacheConfig); final ExecChainHandler t = createCachingExecChain(cache, cacheConfig);
final ExecChain.Scope scope = new ExecChain.Scope("teset", route, get, mockEndpoint, context); final ExecChain.Scope scope = new ExecChain.Scope("teset", route, get, mockEndpoint, context);

View File

@ -56,6 +56,7 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.ByteArrayBuffer;
import org.easymock.Capture; import org.easymock.Capture;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.junit.Assert; import org.junit.Assert;
@ -2617,7 +2618,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
validated.setHeader("Content-Length", "128"); validated.setHeader("Content-Length", "128");
validated.setEntity(new ByteArrayEntity(bytes)); validated.setEntity(new ByteArrayEntity(bytes));
final ClassicHttpResponse reconstructed = HttpTestUtils.make200Response(); final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
final Capture<ClassicHttpRequest> cap = new Capture<>(); final Capture<ClassicHttpRequest> cap = new Capture<>();
@ -2633,12 +2634,13 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
EasyMock.expect(mockCache.getCacheEntry( EasyMock.expect(mockCache.getCacheEntry(
EasyMock.isA(HttpHost.class), EasyMock.isA(HttpHost.class),
EasyMock.isA(ClassicHttpRequest.class))).andReturn(entry).times(0, 1); EasyMock.isA(ClassicHttpRequest.class))).andReturn(entry).times(0, 1);
EasyMock.expect(mockCache.cacheAndReturnResponse( EasyMock.expect(mockCache.createCacheEntry(
EasyMock.isA(HttpHost.class), EasyMock.isA(HttpHost.class),
EasyMock.isA(ClassicHttpRequest.class), EasyMock.isA(ClassicHttpRequest.class),
eqCloseableResponse(validated), eqCloseableResponse(validated),
EasyMock.isA(ByteArrayBuffer.class),
EasyMock.isA(Date.class), EasyMock.isA(Date.class),
EasyMock.isA(Date.class))).andReturn(reconstructed).times(0, 1); EasyMock.isA(Date.class))).andReturn(cacheEntry).times(0, 1);
replayMocks(); replayMocks();
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);

View File

@ -43,6 +43,10 @@ public final class SimpleHttpResponse extends BasicHttpResponse {
super(code); super(code);
} }
public SimpleHttpResponse(final int code, final String reasonPhrase) {
super(code, reasonPhrase);
}
public static SimpleHttpResponse copy(final HttpResponse original) { public static SimpleHttpResponse copy(final HttpResponse original) {
Args.notNull(original, "HTTP response"); Args.notNull(original, "HTTP response");
final SimpleHttpResponse copy = new SimpleHttpResponse(original.getCode()); final SimpleHttpResponse copy = new SimpleHttpResponse(original.getCode());
@ -53,6 +57,34 @@ public final class SimpleHttpResponse extends BasicHttpResponse {
return copy; return copy;
} }
public static SimpleHttpResponse create(final int code) {
return new SimpleHttpResponse(code);
}
public static SimpleHttpResponse create(final int code, final String content, final ContentType contentType) {
final SimpleHttpResponse response = new SimpleHttpResponse(code);
if (content != null) {
response.setBodyText(content, contentType);
}
return response;
}
public static SimpleHttpResponse create(final int code, final String content) {
return create(code, content, ContentType.TEXT_PLAIN);
}
public static SimpleHttpResponse create(final int code, final byte[] content, final ContentType contentType) {
final SimpleHttpResponse response = new SimpleHttpResponse(code);
if (content != null) {
response.setBodyBytes(content, contentType);
}
return response;
}
public static SimpleHttpResponse create(final int code, final byte[] content) {
return create(code, content, ContentType.TEXT_PLAIN);
}
public void setBody(final SimpleBody body) { public void setBody(final SimpleBody body) {
this.body = body; this.body = body;
} }