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/18 07:02:19 UTC

(httpcomponents-client) branch master updated: CacheKeyGenerator to support requests with an enclosed content body such as QUERY

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 66b541b8c CacheKeyGenerator to support requests with an enclosed content body such as QUERY
66b541b8c is described below

commit 66b541b8cd7da3f061da496500a22b9ac65a8916
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Jul 15 15:50:21 2026 +0200

    CacheKeyGenerator to support requests with an enclosed content body such as QUERY
---
 .../client5/http/impl/cache/AsyncCachingExec.java  |  2 +-
 .../http/impl/cache/BasicHttpAsyncCache.java       |  8 +--
 .../hc/client5/http/impl/cache/BasicHttpCache.java |  8 +--
 .../client5/http/impl/cache/CacheKeyGenerator.java | 29 ++++++++++
 .../http/impl/cache/TestBasicHttpAsyncCache.java   | 44 ++++++++-------
 .../http/impl/cache/TestBasicHttpCache.java        | 16 +++---
 .../http/impl/cache/TestCacheKeyGenerator.java     | 64 ++++++++++++----------
 7 files changed, 104 insertions(+), 67 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 42ade554c..f2a41e82c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -298,7 +298,7 @@ public void completed(final CacheMatch result) {
                 final CacheHit root = result != null ? result.root : null;
                 if (hit == null) {
                     if (requestCollapsingEnabled && root == null && !requestCacheControl.isOnlyIfCached()) {
-                        final String cacheKey = CacheKeyGenerator.INSTANCE.generateKey(target, cacheRequest);
+                        final String cacheKey = CacheKeyGenerator.INSTANCE.generateKey(target, cacheRequest, SimpleHttpRequest::getBodyBytes);
                         final CacheRequestCollapser.Token token = collapser.enter(cacheKey);
                         if (token.isLeader()) {
                             handleCacheMiss(requestCacheControl, null, target, cacheRequest, scope, chain, new AsyncExecCallback() {
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 05f4adbb0..658e2754d 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
@@ -94,7 +94,7 @@ public BasicHttpAsyncCache(final ResourceFactory resourceFactory, final HttpAsyn
 
     @Override
     public Cancellable match(final HttpHost host, final SimpleHttpRequest request, final FutureCallback<CacheMatch> callback) {
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         if (LOG.isDebugEnabled()) {
             LOG.debug("Get cache entry: {}", rootKey);
         }
@@ -390,7 +390,7 @@ public Cancellable store(
             final Instant requestSent,
             final Instant responseReceived,
             final FutureCallback<CacheHit> callback) {
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         if (LOG.isDebugEnabled()) {
             LOG.debug("Create cache entry: {}", rootKey);
         }
@@ -465,7 +465,7 @@ public Cancellable storeFromNegotiated(
 
         storeInternal(negotiated.getEntryKey(), updatedEntry, null);
 
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final HttpCacheEntry copy = cacheEntryFactory.copy(updatedEntry);
         final String variantKey = cacheKeyGenerator.generateVariantKey(request, copy);
         return store(rootKey, variantKey, copy, callback);
@@ -551,7 +551,7 @@ public Cancellable evictInvalidatedEntries(
         final int status = response.getCode();
         if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_CLIENT_ERROR &&
                 !Method.isSafe(request.getMethod())) {
-            final String rootKey = cacheKeyGenerator.generateKey(host, request);
+            final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
             evict(rootKey);
             final URI requestUri = CacheSupport.requestUriNormalizedOrNull(host, request);
             if (requestUri != null) {
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 2d0a60fca..2ab2f1656 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
@@ -140,7 +140,7 @@ private void removeInternal(final String cacheKey) {
 
     @Override
     public CacheMatch match(final HttpHost host, final SimpleHttpRequest request) {
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         if (LOG.isDebugEnabled()) {
             LOG.debug("Get cache root entry: {}", rootKey);
         }
@@ -223,7 +223,7 @@ public CacheHit store(
             final ByteArrayBuffer content,
             final Instant requestSent,
             final Instant responseReceived) {
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         if (LOG.isDebugEnabled()) {
             LOG.debug("Create cache entry: {}", rootKey);
         }
@@ -294,7 +294,7 @@ public CacheHit storeFromNegotiated(
                negotiated.entry);
         storeInternal(negotiated.getEntryKey(), updatedEntry);
 
-        final String rootKey = cacheKeyGenerator.generateKey(host, request);
+        final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final HttpCacheEntry copy = cacheEntryFactory.copy(updatedEntry);
         final String variantKey = cacheKeyGenerator.generateVariantKey(request, copy);
         return store(rootKey, variantKey, copy);
@@ -347,7 +347,7 @@ public void evictInvalidatedEntries(final HttpHost host, final SimpleHttpRequest
         final int status = response.getCode();
         if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_CLIENT_ERROR &&
                 !Method.isSafe(request.getMethod())) {
-            final String rootKey = cacheKeyGenerator.generateKey(host, request);
+            final String rootKey = cacheKeyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
             evict(rootKey);
             final URI requestUri = CacheSupport.requestUriNormalizedOrNull(host, request);
             if (requestUri != null) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheKeyGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheKeyGenerator.java
index 51dcf4896..171a420b0 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheKeyGenerator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheKeyGenerator.java
@@ -38,6 +38,7 @@
 import java.util.Spliterators;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.StreamSupport;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
@@ -72,6 +73,10 @@ public class CacheKeyGenerator implements Resolver<URI, String> {
      */
     public static final CacheKeyGenerator INSTANCE = new CacheKeyGenerator();
 
+    /**
+     * @deprecated Do not use.
+     */
+    @Deprecated
     @Override
     public String resolve(final URI uri) {
         return generateKey(uri);
@@ -135,9 +140,33 @@ public String generateKey(final URI requestUri) {
      * @param host The host for this request
      * @param request the {@link HttpRequest}
      * @return cache key
+     *
+     * @deprecated Use {@link #generateKey(HttpHost, HttpRequest, Function)}
      */
+    @Deprecated
     public String generateKey(final HttpHost host, final HttpRequest request) {
+        return generateKey(host, request, r -> null);
+    }
+
+    /**
+     * Computes a root key for the given {@link HttpHost}, {@link HttpRequest} and
+     * request body that can be used as a unique identifier for cached resources.
+     *
+     * @param host The host for this request
+     * @param request the {@link HttpRequest}
+     * @param bodyExtractor function to extract request body. May be {@code null}
+     * @return cache key
+     *
+     * @since 5.7
+     */
+    public <T extends HttpRequest> String generateKey(final HttpHost host,
+                                                      final T request,
+                                                      final Function<T, byte[]> bodyExtractor) {
         final String s = CacheSupport.requestUriRaw(host, request);
+        final byte[] body = bodyExtractor != null ? bodyExtractor.apply(request) : null;
+        if (body != null) {
+            throw new UnsupportedOperationException();
+        }
         try {
             return generateKey(new URI(s));
         } catch (final URISyntaxException ex) {
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 733656595..6e8e985d6 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
@@ -69,6 +69,7 @@ class TestBasicHttpAsyncCache {
     private Instant now;
     private Instant tenSecondsAgo;
     private SimpleHttpAsyncCacheStorage backing;
+    private CacheKeyGenerator keyGenerator;
     private BasicHttpAsyncCache impl;
 
     @BeforeEach
@@ -77,6 +78,7 @@ void setUp() {
         now = Instant.now();
         tenSecondsAgo = now.minusSeconds(10);
         backing = Mockito.spy(new SimpleHttpAsyncCacheStorage());
+        keyGenerator = CacheKeyGenerator.INSTANCE;
         impl = new BasicHttpAsyncCache(HeapResourceFactory.INSTANCE, backing);
     }
 
@@ -102,7 +104,7 @@ void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() throws Exception
         final HttpHost host = new HttpHost("foo.example.com");
         final SimpleHttpRequest request = new SimpleHttpRequest("GET", "http://foo.example.com/bar");
 
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String key = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
 
         backing.map.put(key,entry);
 
@@ -275,7 +277,7 @@ void testGetVariantCacheEntriesReturnsAllVariants() throws Exception {
         final SimpleHttpRequest req1 = new SimpleHttpRequest("GET", uri);
         req1.setHeader("Accept-Encoding", "gzip");
 
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(uri);
+        final String rootKey = keyGenerator.generateKey(uri);
 
         final HttpResponse resp1 = HttpTestUtils.make200Response();
         resp1.setHeader("Date", DateUtils.formatStandardDate(now));
@@ -547,7 +549,7 @@ void testInvalidatesUnsafeRequests() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "POST", "/path");
         final HttpResponse response = HttpTestUtils.make200Response();
 
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String key = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
 
         backing.putEntry(key, HttpTestUtils.makeCacheEntry());
 
@@ -587,7 +589,7 @@ void testDoesNotInvalidateSafeRequests() throws Exception {
     @Test
     void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "POST", "/path");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final Set<String> variants = new HashSet<>();
         variants.add("{var1}");
         variants.add("{var2}");
@@ -618,12 +620,12 @@ void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -650,12 +652,12 @@ void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -702,12 +704,12 @@ void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -737,12 +739,12 @@ void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception
     @Test
     void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -776,7 +778,7 @@ void testDoesNotInvalidateUriSpecifiedByContentLocationOtherOrigin() throws Exce
                 .setHost("bar.example.com")
                 .setPath("/")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -801,7 +803,7 @@ void testDoesNotInvalidateUriSpecifiedByContentLocationIfEtagsMatch() throws Exc
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"same-etag\"");
@@ -828,7 +830,7 @@ void testDoesNotInvalidateUriSpecifiedByContentLocationIfOlder() throws Exceptio
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -855,7 +857,7 @@ void testDoesNotInvalidateUriSpecifiedByContentLocationIfResponseHasNoEtag() thr
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.removeHeaders("ETag");
@@ -882,7 +884,7 @@ void testDoesNotInvalidateUriSpecifiedByContentLocationIfEntryHasNoEtag() throws
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag", "\"some-etag\"");
@@ -908,7 +910,7 @@ void testInvalidatesUriSpecifiedByContentLocationIfResponseHasNoDate() throws Ex
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag", "\"new-etag\"");
@@ -935,7 +937,7 @@ void testInvalidatesUriSpecifiedByContentLocationIfEntryHasNoDate() throws Excep
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -961,7 +963,7 @@ void testInvalidatesUriSpecifiedByContentLocationIfResponseHasMalformedDate() th
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
@@ -988,7 +990,7 @@ void testInvalidatesUriSpecifiedByContentLocationIfEntryHasMalformedDate() throw
                 .setHttpHost(host)
                 .setPath("/bar")
                 .build();
-        final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
+        final String contentKey = keyGenerator.generateKey(contentUri);
 
         final HttpResponse response = HttpTestUtils.make200Response();
         response.setHeader("ETag","\"new-etag\"");
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 f091b7591..429e93ddd 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
@@ -68,6 +68,7 @@ class TestBasicHttpCache {
     private Instant now;
     private Instant tenSecondsAgo;
     private SimpleHttpCacheStorage backing;
+    private CacheKeyGenerator keyGenerator;
     private BasicHttpCache impl;
 
     @BeforeEach
@@ -76,6 +77,7 @@ void setUp() {
         now = Instant.now();
         tenSecondsAgo = now.minusSeconds(10);
         backing = Mockito.spy(new SimpleHttpCacheStorage());
+        keyGenerator = CacheKeyGenerator.INSTANCE;
         impl = new BasicHttpCache(new HeapResourceFactory(), backing);
     }
 
@@ -94,7 +96,7 @@ void testGetCacheEntryFetchesFromCacheOnCacheHitIfNoVariants() {
         final HttpHost host = new HttpHost("foo.example.com");
         final SimpleHttpRequest request = new SimpleHttpRequest("GET", "http://foo.example.com/bar");
 
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String key = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
 
         backing.map.put(key,entry);
 
@@ -417,7 +419,7 @@ void testStoreFromNegotiatedVariant() throws Exception {
     @Test
     void testInvalidatesUnsafeRequests() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "POST","/path");
-        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String key = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
 
         final HttpResponse response = HttpTestUtils.make200Response();
 
@@ -450,7 +452,7 @@ void testDoesNotInvalidateSafeRequests() {
     @Test
     void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "POST","/path");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final Set<String> variants = new HashSet<>();
         variants.add("{var1}");
         variants.add("{var2}");
@@ -478,7 +480,7 @@ void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
@@ -507,7 +509,7 @@ void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
@@ -553,7 +555,7 @@ void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
     @Test
     void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
@@ -585,7 +587,7 @@ void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception
     @Test
     void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
         final SimpleHttpRequest request = new SimpleHttpRequest( "PUT", "/foo");
-        final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
+        final String rootKey = keyGenerator.generateKey(host, request, SimpleHttpRequest::getBodyBytes);
         final URI contentUri = new URIBuilder()
                 .setHttpHost(host)
                 .setPath("/bar")
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheKeyGenerator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheKeyGenerator.java
index b1784e83d..c4c5d99fa 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheKeyGenerator.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheKeyGenerator.java
@@ -51,6 +51,10 @@ class TestCacheKeyGenerator {
 
     private CacheKeyGenerator extractor;
 
+    private String generateKey(final HttpHost host, final HttpRequest req) {
+        return extractor.generateKey(host, req, r -> null);
+    }
+
     @BeforeEach
     void setUp() {
         extractor = CacheKeyGenerator.INSTANCE;
@@ -60,49 +64,49 @@ void setUp() {
     void testExtractsUriFromAbsoluteUriInRequest() {
         final HttpHost host = new HttpHost("bar.example.com");
         final HttpRequest req = new HttpGet("http://foo.example.com/");
-        Assertions.assertEquals("http://foo.example.com:80/", extractor.generateKey(host, req));
+        Assertions.assertEquals("http://foo.example.com:80/", generateKey(host, req));
     }
 
     @Test
     void testGetURIWithDefaultPortAndScheme() {
-        Assertions.assertEquals("http://www.comcast.net:80/", extractor.generateKey(
+        Assertions.assertEquals("http://www.comcast.net:80/", generateKey(
                 new HttpHost("www.comcast.net"),
                 new BasicHttpRequest("GET", "/")));
 
-        Assertions.assertEquals("http://www.fancast.com:80/full_episodes", extractor.generateKey(
+        Assertions.assertEquals("http://www.fancast.com:80/full_episodes", generateKey(
                 new HttpHost("www.fancast.com"),
                 new BasicHttpRequest("GET", "/full_episodes")));
     }
 
     @Test
     void testGetURIWithDifferentScheme() {
-        Assertions.assertEquals("https://www.comcast.net:443/", extractor.generateKey(
+        Assertions.assertEquals("https://www.comcast.net:443/", generateKey(
                 new HttpHost("https", "www.comcast.net", -1),
                 new BasicHttpRequest("GET", "/")));
 
-        Assertions.assertEquals("myhttp://www.fancast.com/full_episodes", extractor.generateKey(
+        Assertions.assertEquals("myhttp://www.fancast.com/full_episodes", generateKey(
                 new HttpHost("myhttp", "www.fancast.com", -1),
                 new BasicHttpRequest("GET", "/full_episodes")));
     }
 
     @Test
     void testGetURIWithDifferentPort() {
-        Assertions.assertEquals("http://www.comcast.net:8080/", extractor.generateKey(
+        Assertions.assertEquals("http://www.comcast.net:8080/", generateKey(
                 new HttpHost("www.comcast.net", 8080),
                 new BasicHttpRequest("GET", "/")));
 
-        Assertions.assertEquals("http://www.fancast.com:9999/full_episodes", extractor.generateKey(
+        Assertions.assertEquals("http://www.fancast.com:9999/full_episodes", generateKey(
                 new HttpHost("www.fancast.com", 9999),
                 new BasicHttpRequest("GET", "/full_episodes")));
     }
 
     @Test
     void testGetURIWithDifferentPortAndScheme() {
-        Assertions.assertEquals("https://www.comcast.net:8080/", extractor.generateKey(
+        Assertions.assertEquals("https://www.comcast.net:8080/", generateKey(
                 new HttpHost("https", "www.comcast.net", 8080),
                 new BasicHttpRequest("GET", "/")));
 
-        Assertions.assertEquals("myhttp://www.fancast.com:9999/full_episodes", extractor.generateKey(
+        Assertions.assertEquals("myhttp://www.fancast.com:9999/full_episodes", generateKey(
                 new HttpHost("myhttp", "www.fancast.com", 9999),
                 new BasicHttpRequest("GET", "/full_episodes")));
     }
@@ -112,7 +116,7 @@ void testEmptyPortEquivalentToDefaultPortForHttp() {
         final HttpHost host1 = new HttpHost("foo.example.com:");
         final HttpHost host2 = new HttpHost("foo.example.com:80");
         final HttpRequest req = new BasicHttpRequest("GET", "/");
-        Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
+        Assertions.assertEquals(generateKey(host1, req), generateKey(host2, req));
     }
 
     @Test
@@ -120,8 +124,8 @@ void testEmptyPortEquivalentToDefaultPortForHttps() {
         final HttpHost host1 = new HttpHost("https", "foo.example.com", -1);
         final HttpHost host2 = new HttpHost("https", "foo.example.com", 443);
         final HttpRequest req = new BasicHttpRequest("GET", "/");
-        final String uri1 = extractor.generateKey(host1, req);
-        final String uri2 = extractor.generateKey(host2, req);
+        final String uri1 = generateKey(host1, req);
+        final String uri2 = generateKey(host2, req);
         Assertions.assertEquals(uri1, uri2);
     }
 
@@ -130,8 +134,8 @@ void testEmptyPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
         final HttpGet get1 = new HttpGet("https://bar.example.com:/");
         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
-        final String uri1 = extractor.generateKey(host, get1);
-        final String uri2 = extractor.generateKey(host, get2);
+        final String uri1 = generateKey(host, get1);
+        final String uri2 = generateKey(host, get2);
         Assertions.assertEquals(uri1, uri2);
     }
 
@@ -140,8 +144,8 @@ void testNotProvidedPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
         final HttpHost host = new HttpHost("https", "foo.example.com", -1);
         final HttpGet get1 = new HttpGet("https://bar.example.com/");
         final HttpGet get2 = new HttpGet("https://bar.example.com:443/");
-        final String uri1 = extractor.generateKey(host, get1);
-        final String uri2 = extractor.generateKey(host, get2);
+        final String uri1 = generateKey(host, get1);
+        final String uri2 = generateKey(host, get2);
         Assertions.assertEquals(uri1, uri2);
     }
 
@@ -150,7 +154,7 @@ void testNotProvidedPortEquivalentToDefaultPortForHttp() {
         final HttpHost host1 = new HttpHost("foo.example.com");
         final HttpHost host2 = new HttpHost("foo.example.com:80");
         final HttpRequest req = new BasicHttpRequest("GET", "/");
-        Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
+        Assertions.assertEquals(generateKey(host1, req), generateKey(host2, req));
     }
 
     @Test
@@ -158,7 +162,7 @@ void testHostNameComparisonsAreCaseInsensitive() {
         final HttpHost host1 = new HttpHost("foo.example.com");
         final HttpHost host2 = new HttpHost("FOO.EXAMPLE.COM");
         final HttpRequest req = new BasicHttpRequest("GET", "/");
-        Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
+        Assertions.assertEquals(generateKey(host1, req), generateKey(host2, req));
     }
 
     @Test
@@ -166,7 +170,7 @@ void testSchemeNameComparisonsAreCaseInsensitive() {
         final HttpHost host1 = new HttpHost("http", "foo.example.com", -1);
         final HttpHost host2 = new HttpHost("HTTP", "foo.example.com", -1);
         final HttpRequest req = new BasicHttpRequest("GET", "/");
-        Assertions.assertEquals(extractor.generateKey(host1, req), extractor.generateKey(host2, req));
+        Assertions.assertEquals(generateKey(host1, req), generateKey(host2, req));
     }
 
     @Test
@@ -174,7 +178,7 @@ void testEmptyAbsPathIsEquivalentToSlash() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
         final HttpRequest req2 = new HttpGet("http://foo.example.com");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -182,7 +186,7 @@ void testExtraDotSegmentsAreIgnored() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
         final HttpRequest req2 = new HttpGet("http://foo.example.com/./");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -190,7 +194,7 @@ void testExtraDotDotSegmentsAreIgnored() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/");
         final HttpRequest req2 = new HttpGet("http://foo.example.com/.././../");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -198,7 +202,7 @@ void testIntermidateDotDotSegementsAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -206,7 +210,7 @@ void testIntermidateEncodedDotDotSegementsAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/home.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/../home.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -214,7 +218,7 @@ void testIntermidateDotSegementsAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/./home.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -222,7 +226,7 @@ void testEquivalentPathEncodingsAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -230,7 +234,7 @@ void testEquivalentExtraPathEncodingsAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
@@ -238,14 +242,14 @@ void testEquivalentExtraPathEncodingsWithPercentAreEquivalent() {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home%20folder.html");
         final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home%20folder.html");
-        Assertions.assertEquals(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
+        Assertions.assertEquals(generateKey(host, req1), generateKey(host, req2));
     }
 
     @Test
     void testGetURIWithQueryParameters() {
-        Assertions.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.generateKey(
+        Assertions.assertEquals("http://www.comcast.net:80/?foo=bar", generateKey(
                 new HttpHost("http", "www.comcast.net", -1), new BasicHttpRequest("GET", "/?foo=bar")));
-        Assertions.assertEquals("http://www.fancast.com:80/full_episodes?foo=bar", extractor.generateKey(
+        Assertions.assertEquals("http://www.fancast.com:80/full_episodes?foo=bar", generateKey(
                 new HttpHost("http", "www.fancast.com", -1), new BasicHttpRequest("GET",
                         "/full_episodes?foo=bar")));
     }