You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2026/07/26 16:53:19 UTC
(httpcomponents-client) branch master updated: Extended HttpCacheEntry and related classes to store the enclosed request body (#859)
This is an automated email from the ASF dual-hosted git repository.
ok2c pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
The following commit(s) were added to refs/heads/master by this push:
new c4eb7e5a4 Extended HttpCacheEntry and related classes to store the enclosed request body (#859)
c4eb7e5a4 is described below
commit c4eb7e5a4fa095c334391138d0462c9674d99984
Author: hunghhdev <hu...@gmail.com>
AuthorDate: Sun Jul 26 23:53:14 2026 +0700
Extended HttpCacheEntry and related classes to store the enclosed request body (#859)
---
.../hc/client5/http/cache/HttpCacheEntry.java | 34 +++++++
.../client5/http/cache/HttpCacheEntryFactory.java | 27 ++++++
.../http/impl/cache/BasicHttpAsyncCache.java | 6 +-
.../hc/client5/http/impl/cache/BasicHttpCache.java | 6 +-
.../cache/HttpByteArrayCacheEntrySerializer.java | 66 +++++++++----
.../http/cache/TestHttpCacheEntryFactory.java | 18 ++++
.../http/impl/cache/HttpCacheEntryMatcher.java | 3 +
.../http/impl/cache/TestBasicHttpAsyncCache.java | 21 +++++
.../http/impl/cache/TestBasicHttpCache.java | 14 +++
.../TestHttpByteArrayCacheEntrySerializer.java | 105 +++++++++++++++++++++
10 files changed, 276 insertions(+), 24 deletions(-)
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
index 5f7f10ee1..d362a379b 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
@@ -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;
@@ -86,6 +87,8 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
/**
* Internal constructor that makes no validation of the input parameters and makes
* no copies of the original client request and the origin response.
+ *
+ * @since 5.7
*/
@Internal
public HttpCacheEntry(
@@ -94,6 +97,7 @@ public HttpCacheEntry(
final String method,
final String requestURI,
final HeaderGroup requestHeaders,
+ final byte[] requestContent,
final int status,
final HeaderGroup responseHeaders,
final Resource resource,
@@ -104,6 +108,7 @@ public HttpCacheEntry(
this.method = method;
this.requestURI = requestURI;
this.requestHeaders = requestHeaders;
+ this.requestContent = requestContent;
this.status = status;
this.responseHeaders = responseHeaders;
this.resource = resource;
@@ -114,6 +119,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
@@ -176,6 +199,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);
@@ -491,6 +515,16 @@ 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.
+ *
+ * @since 5.7
+ */
+ 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,
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java
index 9b86f0bcc..03c01f0a1 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java
@@ -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,
@@ -152,6 +153,29 @@ 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
+ *
+ * @since 5.7
+ */
+ 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");
@@ -169,6 +193,7 @@ public HttpCacheEntry create(final Instant requestInstant,
request.getMethod(),
uri,
requestHeaders,
+ requestContent,
response.getCode(),
responseHeaders,
resource,
@@ -213,6 +238,7 @@ public HttpCacheEntry createUpdated(
request.getMethod(),
uri,
requestHeaders,
+ entry.getRequestContent(),
entry.getStatus(),
mergedHeaders,
entry.getResource(),
@@ -233,6 +259,7 @@ public HttpCacheEntry copy(final HttpCacheEntry entry) {
entry.getRequestMethod(),
entry.getRequestURI(),
headers(entry.requestHeaderIterator()),
+ entry.getRequestContent(),
entry.getStatus(),
headers(entry.headerIterator()),
entry.getResource(),
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
index 658e2754d..fd2887c51 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
@@ -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);
}
@@ -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);
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
index 2ab2f1656..3f112f155 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
@@ -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);
}
@@ -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);
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java
index be0785e60..42dc952fc 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpByteArrayCacheEntrySerializer.java
@@ -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";
@@ -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);
@@ -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(": ");
@@ -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);
@@ -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<>();
@@ -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)) {
@@ -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);
@@ -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");
}
@@ -327,6 +331,7 @@ public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws R
requestLine.getMethod(),
requestLine.getUri(),
requestHeaders,
+ requestContent,
statusLine.getStatusCode(),
responseHeaders,
resource,
@@ -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);
}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java
index 8a0f277cc..09673f2df 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java
@@ -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;
@@ -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);
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java
index d279e465f..a9ec8074c 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java
@@ -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;
}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java
index 6e8e985d6..124573a40 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java
@@ -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;
@@ -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");
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
index 429e93ddd..bba9d5150 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
@@ -48,6 +48,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;
@@ -106,6 +107,19 @@ void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() {
assertSame(entry, result.hit.entry);
}
+ @Test
+ void testStoreRetainsRequestContent() {
+ 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 CacheHit hit = impl.store(host, request, origResponse, HttpTestUtils.makeRandomBuffer(128), now, now);
+ Assertions.assertArrayEquals(request.getBodyBytes(), hit.entry.getRequestContent());
+ }
+
@Test
void testGetCacheEntryReturnsNullIfNoVariantInCache() {
final SimpleHttpRequest origRequest = new SimpleHttpRequest("GET", "http://foo.example.com/bar");
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java
index 9a70a70ba..54f34b463 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpByteArrayCacheEntrySerializer.java
@@ -392,6 +392,111 @@ void testSimpleVariantMap() throws Exception {
HttpCacheEntryMatcher.assertEquivalent(deserialized.getContent(), storageEntry.getContent());
}
+ /**
+ * Serialize and deserialize a cache entry with enclosed request content.
+ */
+ @Test
+ void testSerializeAndDeserializeRequestContent() throws Exception {
+ final String requestContent = "{\"criteria\":\"value\"}";
+ final String content = "Hello World";
+ final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8);
+ final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
+ "QUERY", "/stuff", HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())),
+ requestContent.getBytes(StandardCharsets.UTF_8),
+ HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())),
+ new HeapResource(content.getBytes(contentType.getCharset())),
+ null);
+ final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
+ final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);
+
+ final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(serialized);
+ Assertions.assertEquals(storageEntry.getKey(), deserialized.getKey());
+ HttpCacheEntryMatcher.assertEquivalent(deserialized.getContent(), storageEntry.getContent());
+ }
+
+ /**
+ * Deserialize a cache entry with request content in a fixed format.
+ */
+ @Test
+ void testDeserializeRequestContent() throws Exception {
+ final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
+ "HC-Key: unique-cache-key\n" +
+ "HC-Resource-Length: 11\n" +
+ "HC-Request-Content-Length: 4\n" +
+ "HC-Request-Instant: 1686210849596\n" +
+ "HC-Response-Instant: 1686210849596\n" +
+ "\n" +
+ "QUERY /stuff HTTP/1.1\n" +
+ "Content-Type: text/plain; charset=UTF-8\n" +
+ "\n" +
+ "abcd" +
+ "HTTP/1.1 200 \n" +
+ "Content-Type: text/plain; charset=UTF-8\n" +
+ "\n" +
+ "Hello World";
+ final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
+ final HttpCacheStorageEntry deserialized = httpCacheEntrySerializer.deserialize(bytes1);
+ final HttpCacheEntry cacheEntry = deserialized.getContent();
+ Assertions.assertEquals("QUERY", cacheEntry.getRequestMethod());
+ Assertions.assertArrayEquals("abcd".getBytes(StandardCharsets.UTF_8), cacheEntry.getRequestContent());
+ Assertions.assertArrayEquals("Hello World".getBytes(StandardCharsets.UTF_8), cacheEntry.getResource().get());
+ }
+
+ /**
+ * Deserialize a cache entry with truncated request content.
+ */
+ @Test
+ void testTruncatedRequestContent() {
+ final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
+ "HC-Key: unique-cache-key\n" +
+ "HC-Request-Content-Length: 100\n" +
+ "HC-Request-Instant: 1686210849596\n" +
+ "HC-Response-Instant: 1686210849596\n" +
+ "\n" +
+ "QUERY /stuff HTTP/1.1\n" +
+ "\n" +
+ "abcd";
+ final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
+ final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
+ httpCacheEntrySerializer.deserialize(bytes1));
+ Assertions.assertEquals("Unexpected end of cache content", exception1.getMessage());
+ }
+
+ /**
+ * Deserialize a cache entry with an invalid (negative or exceeding the size
+ * of the serialized representation) request content length.
+ */
+ @Test
+ void testInvalidRequestContentLength() {
+ final String content1 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
+ "HC-Key: unique-cache-key\n" +
+ "HC-Request-Content-Length: -2\n" +
+ "HC-Request-Instant: 1686210849596\n" +
+ "HC-Response-Instant: 1686210849596\n" +
+ "\n" +
+ "QUERY /stuff HTTP/1.1\n" +
+ "\n" +
+ "abcd";
+ final byte[] bytes1 = content1.getBytes(StandardCharsets.UTF_8);
+ final ResourceIOException exception1 = Assertions.assertThrows(ResourceIOException.class, () ->
+ httpCacheEntrySerializer.deserialize(bytes1));
+ Assertions.assertEquals("Invalid cache content length: -2", exception1.getMessage());
+
+ final String content2 = HttpByteArrayCacheEntrySerializer.HC_CACHE_VERSION_LINE + "\n" +
+ "HC-Key: unique-cache-key\n" +
+ "HC-Request-Content-Length: 2000000000\n" +
+ "HC-Request-Instant: 1686210849596\n" +
+ "HC-Response-Instant: 1686210849596\n" +
+ "\n" +
+ "QUERY /stuff HTTP/1.1\n" +
+ "\n" +
+ "abcd";
+ final byte[] bytes2 = content2.getBytes(StandardCharsets.UTF_8);
+ final ResourceIOException exception2 = Assertions.assertThrows(ResourceIOException.class, () ->
+ httpCacheEntrySerializer.deserialize(bytes2));
+ Assertions.assertEquals("Invalid cache content length: 2000000000", exception2.getMessage());
+ }
+
/**
* Deserialize cache entries with trailing garbage.
*/