Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
private final String method;
private final String requestURI;
private final HeaderGroup requestHeaders;
private final byte[] requestContent;
private final int status;
private final HeaderGroup responseHeaders;
private final Resource resource;
Expand All @@ -94,6 +95,7 @@ public HttpCacheEntry(
final String method,
final String requestURI,
final HeaderGroup requestHeaders,
final byte[] requestContent,
final int status,
final HeaderGroup responseHeaders,
final Resource resource,
Expand All @@ -104,6 +106,7 @@ public HttpCacheEntry(
this.method = method;
this.requestURI = requestURI;
this.requestHeaders = requestHeaders;
this.requestContent = requestContent;
this.status = status;
this.responseHeaders = responseHeaders;
this.resource = resource;
Expand All @@ -114,6 +117,24 @@ public HttpCacheEntry(
this.eTagRef = new AtomicReference<>();
}

/**
* Internal constructor that makes no validation of the input parameters and makes
* no copies of the original client request and the origin response.
*/
@Internal
public HttpCacheEntry(
final Instant requestDate,
final Instant responseDate,
final String method,
final String requestURI,
final HeaderGroup requestHeaders,
final int status,
final HeaderGroup responseHeaders,
final Resource resource,
final Collection<String> variants) {
this(requestDate, responseDate, method, requestURI, requestHeaders, null, status, responseHeaders, resource, variants);
}

/**
* Create a new {@link HttpCacheEntry} with variants.
* @param requestDate
Expand Down Expand Up @@ -176,6 +197,7 @@ public HttpCacheEntry(
this.method = Method.GET.name();
this.requestURI = "/";
this.requestHeaders = new HeaderGroup();
this.requestContent = null;
this.status = status;
this.responseHeaders = new HeaderGroup();
this.responseHeaders.setHeaders(responseHeaders);
Expand Down Expand Up @@ -491,6 +513,14 @@ public Iterator<Header> requestHeaderIterator(final String headerName) {
return requestHeaders.headerIterator(headerName);
}

/**
* Returns the content of the original client request or {@code null} if the request
* did not enclose any content.
*/
public byte[] getRequestContent() {
return requestContent;
}

/**
* Tests if the given {@link HttpCacheEntry} is newer than the given {@link MessageHeaders}
* by comparing values of their {@literal DATE} header. In case the given entry, or the message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public HttpCacheEntry createRoot(final HttpCacheEntry latestVariant,
latestVariant.getRequestMethod(),
latestVariant.getRequestURI(),
headers(latestVariant.requestHeaderIterator()),
latestVariant.getRequestContent(),
latestVariant.getStatus(),
headers(latestVariant.headerIterator()),
null,
Expand All @@ -152,6 +153,27 @@ public HttpCacheEntry create(final Instant requestInstant,
final HttpRequest request,
final HttpResponse response,
final Resource resource) {
return create(requestInstant, responseInstant, host, request, null, response, resource);
}

/**
* Create a new {@link HttpCacheEntry} with the given request content and {@link Resource}.
*
* @param requestInstant Date/time when the request was made (Used for age calculations)
* @param responseInstant Date/time that the response came back (Used for age calculations)
* @param host Target host
* @param request Original client request (a deep copy of this object is made)
* @param requestContent Content enclosed in the original client request or {@code null}
* @param response Origin response (a deep copy of this object is made)
* @param resource Resource representing origin response body
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hunghhdev Please add @since 5.7 tags to all new methods. Looks very good otherwise.

public HttpCacheEntry create(final Instant requestInstant,
final Instant responseInstant,
final HttpHost host,
final HttpRequest request,
final byte[] requestContent,
final HttpResponse response,
final Resource resource) {
Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant");
Args.notNull(host, "Host");
Expand All @@ -169,6 +191,7 @@ public HttpCacheEntry create(final Instant requestInstant,
request.getMethod(),
uri,
requestHeaders,
requestContent,
response.getCode(),
responseHeaders,
resource,
Expand Down Expand Up @@ -213,6 +236,7 @@ public HttpCacheEntry createUpdated(
request.getMethod(),
uri,
requestHeaders,
entry.getRequestContent(),
entry.getStatus(),
mergedHeaders,
entry.getResource(),
Expand All @@ -233,6 +257,7 @@ public HttpCacheEntry copy(final HttpCacheEntry entry) {
entry.getRequestMethod(),
entry.getRequestURI(),
headers(entry.requestHeaderIterator()),
entry.getRequestContent(),
entry.getStatus(),
headers(entry.headerIterator()),
entry.getResource(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,8 @@ public Cancellable store(
final Instant requestSent,
final Instant responseReceived,
final FutureCallback<CacheHit> callback) {
final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
final byte[] requestContent = request.getBodyBytes();
final String rootKey = cacheKeyGenerator.generateKey(host, request, r -> requestContent);
if (LOG.isDebugEnabled()) {
LOG.debug("Create cache entry: {}", rootKey);
}
Expand All @@ -410,12 +411,13 @@ public Cancellable store(
responseReceived,
host,
request,
requestContent,
originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
callback.completed(new CacheHit(rootKey, backup));
return Operations.nonCancellable();
}
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, originResponse, resource);
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, requestContent, originResponse, resource);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
return store(rootKey,variantKey, entry, callback);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ public CacheHit store(
final ByteArrayBuffer content,
final Instant requestSent,
final Instant responseReceived) {
final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
final byte[] requestContent = request.getBodyBytes();
final String rootKey = cacheKeyGenerator.generateKey(host, request, r -> requestContent);
if (LOG.isDebugEnabled()) {
LOG.debug("Create cache entry: {}", rootKey);
}
Expand All @@ -243,11 +244,12 @@ public CacheHit store(
responseReceived,
host,
request,
requestContent,
originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
return new CacheHit(rootKey, backup);
}
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, originResponse, resource);
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, requestContent, originResponse, resource);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
return store(rootKey,variantKey, entry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ

static final String HC_CACHE_KEY = "HC-Key";
static final String HC_CACHE_LENGTH = "HC-Resource-Length";
static final String HC_REQUEST_CONTENT_LENGTH = "HC-Request-Content-Length";
static final String HC_REQUEST_INSTANT = "HC-Request-Instant";
static final String HC_RESPONSE_INSTANT = "HC-Response-Instant";
static final String HC_VARIANT = "HC-Variant";
Expand Down Expand Up @@ -133,8 +134,10 @@ public byte[] serialize(final HttpCacheStorageEntry storageEntry) throws Resourc
final String key = storageEntry.getKey();
final HttpCacheEntry cacheEntry = storageEntry.getContent();
final Resource resource = cacheEntry.getResource();
final byte[] requestContent = cacheEntry.getRequestContent();

try (ByteArrayOutputStream out = new ByteArrayOutputStream((resource != null ? (int) resource.length() : 0) + DEFAULT_BUFFER_SIZE)) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream((resource != null ? (int) resource.length() : 0)
+ (requestContent != null ? requestContent.length : 0) + DEFAULT_BUFFER_SIZE)) {
final SessionOutputBuffer outputBuffer = new SessionOutputBufferImpl(bufferSize);
final CharArrayBuffer line = new CharArrayBuffer(DEFAULT_BUFFER_SIZE);

Expand All @@ -155,6 +158,14 @@ public byte[] serialize(final HttpCacheStorageEntry storageEntry) throws Resourc
outputBuffer.writeLine(line, out);
}

if (requestContent != null) {
line.clear();
line.append(HC_REQUEST_CONTENT_LENGTH);
line.append(": ");
line.append(asStr(requestContent.length));
outputBuffer.writeLine(line, out);
}

line.clear();
line.append(HC_REQUEST_INSTANT);
line.append(": ");
Expand Down Expand Up @@ -189,6 +200,11 @@ public byte[] serialize(final HttpCacheStorageEntry storageEntry) throws Resourc
line.clear();
outputBuffer.writeLine(line, out);

if (requestContent != null) {
outputBuffer.flush(out);
out.write(requestContent);
}

line.clear();
final StatusLine statusLine = new StatusLine(HttpVersion.HTTP_1_1, cacheEntry.getStatus(), "");
lineFormatter.formatStatusLine(line, statusLine);
Expand Down Expand Up @@ -241,6 +257,7 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
}
String storageKey = null;
long length = -1;
long requestContentLength = -1;
Instant requestDate = null;
Instant responseDate = null;
final Set<String> variants = new HashSet<>();
Expand All @@ -258,6 +275,8 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
storageKey = value;
} else if (name.equalsIgnoreCase(HC_CACHE_LENGTH)) {
length = asLong(value);
} else if (name.equalsIgnoreCase(HC_REQUEST_CONTENT_LENGTH)) {
requestContentLength = asLong(value);
} else if (name.equalsIgnoreCase(HC_REQUEST_INSTANT)) {
requestDate = asInstant(value);
} else if (name.equalsIgnoreCase(HC_RESPONSE_INSTANT)) {
Expand Down Expand Up @@ -285,6 +304,8 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
}
requestHeaders.addHeader(lineParser.parseHeader(line));
}
final byte[] requestContent = requestContentLength != -1 ?
readBytes(inputBuffer, in, requestContentLength, serializedObject.length) : null;
line.clear();
checkReadResult(inputBuffer.readLine(line, in));
final StatusLine statusLine = lineParser.parseStatusLine(line);
Expand All @@ -298,25 +319,8 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
responseHeaders.addHeader(lineParser.parseHeader(line));
}

final Resource resource;
if (length != -1) {
int off = 0;
int remaining = (int) length;
final byte[] buf = new byte[remaining];
while (remaining > 0) {
final int i = inputBuffer.read(buf, off, remaining, in);
if (i > 0) {
off += i;
remaining -= i;
}
if (i == -1) {
throw new ResourceIOException("Unexpected end of cache content");
}
}
resource = new HeapResource(buf);
} else {
resource = null;
}
final Resource resource = length != -1 ?
new HeapResource(readBytes(inputBuffer, in, length, serializedObject.length)) : null;
if (inputBuffer.read(in) != -1) {
throw new ResourceIOException("Unexpected content at the end of cache content");
}
Expand All @@ -327,6 +331,7 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
requestLine.getMethod(),
requestLine.getUri(),
requestHeaders,
requestContent,
statusLine.getStatusCode(),
responseHeaders,
resource,
Expand All @@ -347,6 +352,27 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
}
}

private static byte[] readBytes(final SessionInputBufferImpl inputBuffer, final InputStream in,
final long length, final int limit) throws IOException {
if (length < 0 || length > limit) {
throw new ResourceIOException("Invalid cache content length: " + length);
}
int off = 0;
int remaining = (int) length;
final byte[] buf = new byte[remaining];
while (remaining > 0) {
final int i = inputBuffer.read(buf, off, remaining, in);
if (i > 0) {
off += i;
remaining -= i;
}
if (i == -1) {
throw new ResourceIOException("Unexpected end of cache content");
}
}
return buf;
}

private static String asStr(final long value) {
return Long.toString(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
*/
package org.apache.hc.client5.http.cache;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -280,6 +281,23 @@ void testCreateResourceEntry() {
Assertions.assertFalse(newEntry.hasVariants());
}

@Test
void testCreateEntryWithRequestContent() {
final byte[] requestContent = "{\"criteria\":\"value\"}".getBytes(StandardCharsets.UTF_8);
final Resource resource = HttpTestUtils.makeRandomResource(128);
final HttpRequest queryRequest = new BasicHttpRequest("QUERY", "/stuff");
final HttpResponse okResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");

final HttpCacheEntry newEntry = impl.create(tenSecondsAgo, oneSecondAgo, host, queryRequest, requestContent, okResponse, resource);
Assertions.assertArrayEquals(requestContent, newEntry.getRequestContent());

final HttpCacheEntry updatedEntry = impl.createUpdated(oneSecondAgo, now, host, queryRequest, response, newEntry);
Assertions.assertArrayEquals(requestContent, updatedEntry.getRequestContent());

final HttpCacheEntry copy = impl.copy(newEntry);
Assertions.assertArrayEquals(requestContent, copy.getRequestContent());
}

@Test
void testCreateUpdatedResourceEntry() {
final Resource resource = HttpTestUtils.makeRandomResource(128);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ private static boolean equivalent(final HttpCacheEntry expectedValue, final Http
if (!headersEqual(expectedValue.requestHeaderIterator(), otherValue.requestHeaderIterator())) {
return false;
}
if (!Arrays.equals(expectedValue.getRequestContent(), otherValue.getRequestContent())) {
return false;
}
if (!instantEqual(expectedValue.getRequestInstant(), otherValue.getRequestInstant())) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
Expand Down Expand Up @@ -121,6 +122,26 @@ void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception
assertSame(entry, result.hit.entry);
}

@Test
void testStoreRetainsRequestContent() throws Exception {
final SimpleHttpRequest request = new SimpleHttpRequest("QUERY", "http://foo.example.com/bar");
request.setBody("{\"criteria\":\"value\"}", ContentType.APPLICATION_JSON);

final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
origResponse.setHeader("Cache-Control", "max-age=3600, public");

final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<CacheHit> hitRef = new AtomicReference<>();
impl.store(host, request, origResponse, HttpTestUtils.makeRandomBuffer(128), now, now,
HttpTestUtils.countDown(latch, hitRef::set));

latch.await();
final CacheHit hit = hitRef.get();
assertNotNull(hit);
Assertions.assertArrayEquals(request.getBodyBytes(), hit.entry.getRequestContent());
}

@Test
void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
final SimpleHttpRequest origRequest = new SimpleHttpRequest("GET", "http://foo.example.com/bar");
Expand Down
Loading
Loading