HTTPCLIENT-2277: Revision and optimization of cache key generation

This commit is contained in:
Oleg Kalnichevski 2023-06-21 17:52:57 +02:00
parent 0c79379827
commit abbfd8202a
5 changed files with 240 additions and 308 deletions

View File

@ -28,6 +28,7 @@ package org.apache.hc.client5.http.impl.cache;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -90,11 +91,15 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@Override @Override
public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) { public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
if (cacheEntry == null) { final String root = cacheKeyGenerator.generateKey(host, request);
return cacheKeyGenerator.generateKey(host, request); if (cacheEntry != null && cacheEntry.isVariantRoot()) {
} else { final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
return cacheKeyGenerator.generateKey(host, request, cacheEntry); if (!variantNames.isEmpty()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
return variantKey + root;
}
} }
return root;
} }
@Override @Override
@ -104,8 +109,8 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request)); LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
} }
if (!Method.isSafe(request.getMethod())) { if (!Method.isSafe(request.getMethod())) {
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
return storage.removeEntry(cacheKey, new FutureCallback<Boolean>() { return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
@Override @Override
public void completed(final Boolean result) { public void completed(final Boolean result) {
@ -116,7 +121,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
public void failed(final Exception ex) { public void failed(final Exception ex) {
if (ex instanceof ResourceIOException) { if (ex instanceof ResourceIOException) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error removing cache entry with key {}", cacheKey); LOG.warn("I/O error removing cache entry with key {}", rootKey);
} }
callback.completed(Boolean.TRUE); callback.completed(Boolean.TRUE);
} else { } else {
@ -158,18 +163,17 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
Cancellable storeInCache( Cancellable storeInCache(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
final Instant responseReceived, final Instant responseReceived,
final String cacheKey, final String rootKey,
final HttpCacheEntry entry, final HttpCacheEntry entry,
final FutureCallback<Boolean> callback) { final FutureCallback<Boolean> callback) {
if (entry.hasVariants()) { if (entry.hasVariants()) {
return storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry, callback); return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
} else { } else {
return storeEntry(cacheKey, entry, callback); return storeEntry(rootKey, entry, callback);
} }
} }
@ -205,21 +209,21 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
Cancellable storeVariantEntry( Cancellable storeVariantEntry(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
final Instant responseReceived, final Instant responseReceived,
final String cacheKey, final String rootKey,
final HttpCacheEntry entry, final HttpCacheEntry entry,
final FutureCallback<Boolean> callback) { final FutureCallback<Boolean> callback) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry); final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, request, entry); final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String variantCacheKey = variantKey + rootKey;
return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() { return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
@Override @Override
public void completed(final Boolean result) { public void completed(final Boolean result) {
storage.updateEntry(cacheKey, storage.updateEntry(rootKey,
existing -> { existing -> {
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>(); final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
variantMap.put(variantKey, variantCacheKey); variantMap.put(variantKey, variantCacheKey);
@ -236,11 +240,11 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
public void failed(final Exception ex) { public void failed(final Exception ex) {
if (ex instanceof HttpCacheUpdateException) { if (ex instanceof HttpCacheUpdateException) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("Cannot update cache entry with key {}", cacheKey); LOG.warn("Cannot update cache entry with key {}", rootKey);
} }
} else if (ex instanceof ResourceIOException) { } else if (ex instanceof ResourceIOException) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey); LOG.warn("I/O error updating cache entry with key {}", rootKey);
} }
} else { } else {
callback.failed(ex); callback.failed(ex);
@ -287,8 +291,8 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry); LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
return storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry, callback); return storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
} }
@Override @Override
@ -303,19 +307,18 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent, requestSent,
responseReceived, responseReceived,
originResponse, originResponse,
stale); stale);
return storeInCache( return storeInCache(
host,
request, request,
originResponse, originResponse,
requestSent, requestSent,
responseReceived, responseReceived,
cacheKey, rootKey,
updatedEntry, updatedEntry,
new FutureCallback<Boolean>() { new FutureCallback<Boolean>() {
@ -349,13 +352,13 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry); LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent, requestSent,
responseReceived, responseReceived,
originResponse, originResponse,
entry); entry);
return storeEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() { return storeEntry(rootKey, updatedEntry, new FutureCallback<Boolean>() {
@Override @Override
public void completed(final Boolean result) { public void completed(final Boolean result) {
@ -387,17 +390,16 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
try { try {
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null); content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
return storeInCache( return storeInCache(
host,
request, request,
originResponse, originResponse,
requestSent, requestSent,
responseReceived, responseReceived,
cacheKey, rootKey,
entry, new FutureCallback<Boolean>() { entry, new FutureCallback<Boolean>() {
@Override @Override
@ -418,7 +420,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
}); });
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error creating cache entry with key {}", cacheKey); LOG.warn("I/O error creating cache entry with key {}", rootKey);
} }
callback.completed(cacheEntryFactory.create( callback.completed(cacheEntryFactory.create(
requestSent, requestSent,
@ -436,14 +438,15 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
} }
final ComplexCancellable complexCancellable = new ComplexCancellable(); final ComplexCancellable complexCancellable = new ComplexCancellable();
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() { complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
@Override @Override
public void completed(final HttpCacheEntry root) { public void completed(final HttpCacheEntry root) {
if (root != null) { if (root != null) {
if (root.isVariantRoot()) { if (root.isVariantRoot()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, root); final List<String> variantNames = CacheKeyGenerator.variantNames(root);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String variantCacheKey = root.getVariantMap().get(variantKey); final String variantCacheKey = root.getVariantMap().get(variantKey);
if (variantCacheKey != null) { if (variantCacheKey != null) {
complexCancellable.setDependency(storage.getEntry( complexCancellable.setDependency(storage.getEntry(
@ -484,7 +487,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
public void failed(final Exception ex) { public void failed(final Exception ex) {
if (ex instanceof ResourceIOException) { if (ex instanceof ResourceIOException) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey); LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
} }
callback.completed(null); callback.completed(null);
} else { } else {
@ -508,9 +511,9 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request)); LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
} }
final ComplexCancellable complexCancellable = new ComplexCancellable(); final ComplexCancellable complexCancellable = new ComplexCancellable();
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final Map<String, Variant> variants = new HashMap<>(); final Map<String, Variant> variants = new HashMap<>();
complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() { complexCancellable.setDependency(storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
@Override @Override
public void completed(final HttpCacheEntry rootEntry) { public void completed(final HttpCacheEntry rootEntry) {
@ -560,7 +563,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
public void failed(final Exception ex) { public void failed(final Exception ex) {
if (ex instanceof ResourceIOException) { if (ex instanceof ResourceIOException) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey); LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
} }
callback.completed(variants); callback.completed(variants);
} else { } else {

View File

@ -28,6 +28,7 @@ package org.apache.hc.client5.http.impl.cache;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
@ -93,11 +94,15 @@ class BasicHttpCache implements HttpCache {
@Override @Override
public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) { public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
if (cacheEntry == null) { final String root = cacheKeyGenerator.generateKey(host, request);
return cacheKeyGenerator.generateKey(host, request); if (cacheEntry != null && cacheEntry.isVariantRoot()) {
} else { final List<String> variantNames = CacheKeyGenerator.variantNames(cacheEntry);
return cacheKeyGenerator.generateKey(host, request, cacheEntry); if (!variantNames.isEmpty()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
return variantKey + root;
}
} }
return root;
} }
@Override @Override
@ -106,12 +111,12 @@ class BasicHttpCache implements HttpCache {
LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request)); LOG.debug("Flush cache entries: {}; {}", host, new RequestLine(request));
} }
if (!Method.isSafe(request.getMethod())) { if (!Method.isSafe(request.getMethod())) {
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
try { try {
storage.removeEntry(cacheKey); storage.removeEntry(rootKey);
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error removing cache entry with key {}", cacheKey); LOG.warn("I/O error removing cache entry with key {}", rootKey);
} }
} }
} }
@ -136,17 +141,16 @@ class BasicHttpCache implements HttpCache {
} }
void storeInCache( void storeInCache(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
final Instant responseReceived, final Instant responseReceived,
final String cacheKey, final String rootKey,
final HttpCacheEntry entry) { final HttpCacheEntry entry) {
if (entry.hasVariants()) { if (entry.hasVariants()) {
storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry); storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
} else { } else {
storeEntry(cacheKey, entry); storeEntry(rootKey, entry);
} }
} }
@ -161,29 +165,29 @@ class BasicHttpCache implements HttpCache {
} }
void storeVariantEntry( void storeVariantEntry(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
final Instant responseReceived, final Instant responseReceived,
final String cacheKey, final String rootKey,
final HttpCacheEntry entry) { final HttpCacheEntry entry) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry); final List<String> variantNames = CacheKeyGenerator.variantNames(entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, request, entry); final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String variantCacheKey = variantKey + request;
storeEntry(variantCacheKey, entry); storeEntry(variantCacheKey, entry);
try { try {
storage.updateEntry(cacheKey, existing -> { storage.updateEntry(rootKey, existing -> {
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>(); final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
variantMap.put(variantKey, variantCacheKey); variantMap.put(variantKey, variantCacheKey);
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap); return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
}); });
} catch (final HttpCacheUpdateException ex) { } catch (final HttpCacheUpdateException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("Cannot update cache entry with key {}", cacheKey); LOG.warn("Cannot update cache entry with key {}", rootKey);
} }
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey); LOG.warn("I/O error updating cache entry with key {}", rootKey);
} }
} }
} }
@ -199,8 +203,8 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry); LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry); storeVariantEntry(request, originResponse, requestSent, responseReceived, rootKey, entry);
} }
@Override @Override
@ -214,13 +218,13 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent, requestSent,
responseReceived, responseReceived,
originResponse, originResponse,
stale); stale);
storeInCache(host, request, originResponse, requestSent, responseReceived, cacheKey, updatedEntry); storeInCache(request, originResponse, requestSent, responseReceived, rootKey, updatedEntry);
return updatedEntry; return updatedEntry;
} }
@ -235,13 +239,13 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry); LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent, requestSent,
responseReceived, responseReceived,
originResponse, originResponse,
entry); entry);
storeEntry(cacheKey, updatedEntry); storeEntry(rootKey, updatedEntry);
return updatedEntry; return updatedEntry;
} }
@ -256,15 +260,15 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Create cache entry: {}; {}", host, new RequestLine(request));
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
try { try {
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null); content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
storeInCache(host, request, originResponse, requestSent, responseReceived, cacheKey, entry); storeInCache(request, originResponse, requestSent, responseReceived, rootKey, entry);
return entry; return entry;
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error creating cache entry with key {}", cacheKey); LOG.warn("I/O error creating cache entry with key {}", rootKey);
} }
return cacheEntryFactory.create( return cacheEntryFactory.create(
requestSent, requestSent,
@ -280,13 +284,13 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request)); LOG.debug("Get cache entry: {}; {}", host, new RequestLine(request));
} }
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry root; final HttpCacheEntry root;
try { try {
root = storage.getEntry(cacheKey); root = storage.getEntry(rootKey);
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey); LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
} }
return null; return null;
} }
@ -294,7 +298,8 @@ class BasicHttpCache implements HttpCache {
return null; return null;
} }
if (root.isVariantRoot()) { if (root.isVariantRoot()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, root); final List<String> variantNames = CacheKeyGenerator.variantNames(root);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String variantCacheKey = root.getVariantMap().get(variantKey); final String variantCacheKey = root.getVariantMap().get(variantKey);
if (variantCacheKey != null) { if (variantCacheKey != null) {
try { try {
@ -317,13 +322,13 @@ class BasicHttpCache implements HttpCache {
LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request)); LOG.debug("Get variant cache entries: {}; {}", host, new RequestLine(request));
} }
final Map<String,Variant> variants = new HashMap<>(); final Map<String,Variant> variants = new HashMap<>();
final String cacheKey = cacheKeyGenerator.generateKey(host, request); final String rootKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry root; final HttpCacheEntry root;
try { try {
root = storage.getEntry(cacheKey); root = storage.getEntry(rootKey);
} catch (final ResourceIOException ex) { } catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) { if (LOG.isWarnEnabled()) {
LOG.warn("I/O error retrieving cache entry with key {}", cacheKey); LOG.warn("I/O error retrieving cache entry with key {}", rootKey);
} }
return variants; return variants;
} }

View File

@ -26,26 +26,27 @@
*/ */
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.core5.annotation.Contract; 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.function.Resolver; import org.apache.hc.core5.function.Resolver;
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.HttpHeaders; 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.message.MessageSupport; import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.net.PercentCodec;
import org.apache.hc.core5.util.Args;
/** /**
* @since 4.1 * @since 4.1
@ -78,7 +79,7 @@ public class CacheKeyGenerator implements Resolver<URI, String> {
} }
/** /**
* Computes a key for the given {@link HttpHost} and {@link HttpRequest} * Computes a root key for the given {@link HttpHost} and {@link HttpRequest}
* that can be used as a unique identifier for cached resources. * that can be used as a unique identifier for cached resources.
* *
* @param host The host for this request * @param host The host for this request
@ -94,18 +95,64 @@ public class CacheKeyGenerator implements Resolver<URI, String> {
} }
} }
private String getFullHeaderValue(final Header[] headers) { /**
if (headers == null) { * Returns all variant names contained in {@literal VARY} headers of the given message.
return ""; *
* @since 5.3
*/
public static List<String> variantNames(final MessageHeaders message) {
if (message == null) {
return null;
} }
final StringBuilder buf = new StringBuilder(); final List<String> names = new ArrayList<>();
for (int i = 0; i < headers.length; i++) { for (final Iterator<Header> it = message.headerIterator(HttpHeaders.VARY); it.hasNext(); ) {
final Header hdr = headers[i]; final Header header = it.next();
if (i > 0) { CacheSupport.parseTokens(header, names::add);
buf.append(", ");
}
buf.append(hdr.getValue().trim());
} }
return names;
}
/**
* Computes a "variant key" for the given request and the given variants.
* @param request originating request
* @param variantNames variant names
* @return variant key
*
* @since 5.3
*/
public String generateVariantKey(final HttpRequest request, final Collection<String> variantNames) {
Args.notNull(variantNames, "Variant names");
final StringBuilder buf = new StringBuilder("{");
final AtomicBoolean firstHeader = new AtomicBoolean();
variantNames.stream()
.map(h -> h.toLowerCase(Locale.ROOT))
.sorted()
.distinct()
.forEach(h -> {
if (!firstHeader.compareAndSet(false, true)) {
buf.append("&");
}
buf.append(PercentCodec.encode(h, StandardCharsets.UTF_8)).append("=");
final List<String> tokens = new ArrayList<>();
final Iterator<Header> headerIterator = request.headerIterator(h);
while (headerIterator.hasNext()) {
final Header header = headerIterator.next();
CacheSupport.parseTokens(header, tokens::add);
}
final AtomicBoolean firstToken = new AtomicBoolean();
tokens.stream()
.filter(t -> !t.isEmpty())
.map(t -> t.toLowerCase(Locale.ROOT))
.sorted()
.distinct()
.forEach(t -> {
if (!firstToken.compareAndSet(false, true)) {
buf.append(",");
}
buf.append(PercentCodec.encode(t, StandardCharsets.UTF_8));
});
});
buf.append("}");
return buf.toString(); return buf.toString();
} }
@ -118,12 +165,18 @@ public class CacheKeyGenerator implements Resolver<URI, String> {
* @param request the {@link HttpRequest} * @param request the {@link HttpRequest}
* @param entry the parent entry used to track the variants * @param entry the parent entry used to track the variants
* @return cache key * @return cache key
*
* @deprecated Use {@link #generateKey(HttpHost, HttpRequest)} or {@link #generateVariantKey(HttpRequest, Collection)}
*/ */
@Deprecated
public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry) { public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry) {
if (!entry.hasVariants()) { final String rootKey = generateKey(host, request);
return generateKey(host, request); final List<String> variantNames = variantNames(entry);
if (variantNames.isEmpty()) {
return rootKey;
} else {
return generateVariantKey(request, variantNames) + rootKey;
} }
return generateVariantKey(request, entry) + generateKey(host, request);
} }
/** /**
@ -134,35 +187,12 @@ public class CacheKeyGenerator implements Resolver<URI, String> {
* @param req originating request * @param req originating request
* @param entry cache entry in question that has variants * @param entry cache entry in question that has variants
* @return variant key * @return variant key
*
* @deprecated Use {@link #generateVariantKey(HttpRequest, Collection)}.
*/ */
@Deprecated
public String generateVariantKey(final HttpRequest req, final HttpCacheEntry entry) { public String generateVariantKey(final HttpRequest req, final HttpCacheEntry entry) {
final List<String> variantHeaderNames = new ArrayList<>(); return generateVariantKey(req, variantNames(entry));
final Iterator<HeaderElement> it = MessageSupport.iterate(entry, HttpHeaders.VARY);
while (it.hasNext()) {
final HeaderElement elt = it.next();
variantHeaderNames.add(elt.getName());
}
Collections.sort(variantHeaderNames);
final StringBuilder buf;
try {
buf = new StringBuilder("{");
boolean first = true;
for (final String headerName : variantHeaderNames) {
if (!first) {
buf.append("&");
}
buf.append(URLEncoder.encode(headerName, StandardCharsets.UTF_8.name()));
buf.append("=");
buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
StandardCharsets.UTF_8.name()));
first = false;
}
buf.append("}");
} catch (final UnsupportedEncodingException uee) {
throw new RuntimeException("couldn't encode to UTF-8", uee);
}
return buf.toString();
} }
} }

View File

@ -187,7 +187,7 @@ public class TestBasicHttpCache {
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req); final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
impl.storeInCache(host, req, resp, Instant.now(), Instant.now(), key, entry); impl.storeInCache(req, resp, Instant.now(), Instant.now(), key, entry);
assertSame(entry, backing.map.get(key)); assertSame(entry, backing.map.get(key));
} }

View File

@ -26,18 +26,15 @@
*/ */
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import static org.mockito.Mockito.mock; import java.util.Arrays;
import static org.mockito.Mockito.verify; import java.util.Collections;
import static org.mockito.Mockito.when;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.core5.http.Header; 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.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHeaderIterator;
import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.support.BasicRequestBuilder;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -45,20 +42,12 @@ import org.junit.jupiter.api.Test;
@SuppressWarnings({"boxing","static-access"}) // this is test code @SuppressWarnings({"boxing","static-access"}) // this is test code
public class TestCacheKeyGenerator { public class TestCacheKeyGenerator {
private static final BasicHttpRequest REQUEST_FULL_EPISODES = new BasicHttpRequest("GET",
"/full_episodes");
private static final BasicHttpRequest REQUEST_ROOT = new BasicHttpRequest("GET", "/");
private CacheKeyGenerator extractor; private CacheKeyGenerator extractor;
private HttpHost defaultHost; private HttpHost defaultHost;
private HttpCacheEntry mockEntry;
private HttpRequest mockRequest;
@BeforeEach @BeforeEach
public void setUp() throws Exception { public void setUp() throws Exception {
defaultHost = new HttpHost("foo.example.com"); defaultHost = new HttpHost("foo.example.com");
mockEntry = mock(HttpCacheEntry.class);
mockRequest = mock(HttpRequest.class);
extractor = CacheKeyGenerator.INSTANCE; extractor = CacheKeyGenerator.INSTANCE;
} }
@ -71,203 +60,46 @@ public class TestCacheKeyGenerator {
@Test @Test
public void testGetURIWithDefaultPortAndScheme() { public void testGetURIWithDefaultPortAndScheme() {
Assertions.assertEquals("http://www.comcast.net:80/", extractor.generateKey(new HttpHost( Assertions.assertEquals("http://www.comcast.net:80/", extractor.generateKey(
"www.comcast.net"), REQUEST_ROOT)); new HttpHost("www.comcast.net"),
new BasicHttpRequest("GET", "/")));
Assertions.assertEquals("http://www.fancast.com:80/full_episodes", extractor.generateKey(new HttpHost( Assertions.assertEquals("http://www.fancast.com:80/full_episodes", extractor.generateKey(
"www.fancast.com"), REQUEST_FULL_EPISODES)); new HttpHost("www.fancast.com"),
new BasicHttpRequest("GET", "/full_episodes")));
} }
@Test @Test
public void testGetURIWithDifferentScheme() { public void testGetURIWithDifferentScheme() {
Assertions.assertEquals("https://www.comcast.net:443/", extractor.generateKey( Assertions.assertEquals("https://www.comcast.net:443/", extractor.generateKey(
new HttpHost("https", "www.comcast.net", -1), REQUEST_ROOT)); 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", extractor.generateKey(
new HttpHost("myhttp", "www.fancast.com", -1), REQUEST_FULL_EPISODES)); new HttpHost("myhttp", "www.fancast.com", -1),
new BasicHttpRequest("GET", "/full_episodes")));
} }
@Test @Test
public void testGetURIWithDifferentPort() { public void testGetURIWithDifferentPort() {
Assertions.assertEquals("http://www.comcast.net:8080/", extractor.generateKey(new HttpHost( Assertions.assertEquals("http://www.comcast.net:8080/", extractor.generateKey(
"www.comcast.net", 8080), REQUEST_ROOT)); 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", extractor.generateKey(
new HttpHost("www.fancast.com", 9999), REQUEST_FULL_EPISODES)); new HttpHost("www.fancast.com", 9999),
new BasicHttpRequest("GET", "/full_episodes")));
} }
@Test @Test
public void testGetURIWithDifferentPortAndScheme() { public void testGetURIWithDifferentPortAndScheme() {
Assertions.assertEquals("https://www.comcast.net:8080/", extractor.generateKey( Assertions.assertEquals("https://www.comcast.net:8080/", extractor.generateKey(
new HttpHost("https", "www.comcast.net", 8080), REQUEST_ROOT)); 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", extractor.generateKey(
new HttpHost("myhttp", "www.fancast.com", 9999), REQUEST_FULL_EPISODES)); new HttpHost("myhttp", "www.fancast.com", 9999),
} new BasicHttpRequest("GET", "/full_episodes")));
@Test
public void testGetURIWithQueryParameters() {
Assertions.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.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(
new HttpHost("http", "www.fancast.com", -1), new BasicHttpRequest("GET",
"/full_episodes?foo=bar")));
}
@Test
public void testGetVariantURIWithNoVaryHeaderReturnsNormalURI() {
final String theURI = "theURI";
when(mockEntry.hasVariants()).thenReturn(false);
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
Assertions.assertSame(theURI, result);
}
@Test
public void testGetVariantURIWithSingleValueVaryHeaderPrepends() {
final String theURI = "theURI";
final Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
when(mockEntry.hasVariants()).thenReturn(true);
when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
verify(mockEntry).headerIterator("Vary");
verify(mockRequest).getHeaders("Accept-Encoding");
Assertions.assertEquals("{Accept-Encoding=gzip}" + theURI, result);
}
@Test
public void testGetVariantURIWithMissingRequestHeader() {
final String theURI = "theURI";
final Header[] noHeaders = new Header[0];
final Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
when(mockEntry.hasVariants()).thenReturn(true);
when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
when(mockRequest.getHeaders("Accept-Encoding"))
.thenReturn(noHeaders);
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
verify(mockEntry).headerIterator("Vary");
verify(mockRequest).getHeaders("Accept-Encoding");
Assertions.assertEquals("{Accept-Encoding=}" + theURI, result);
}
@Test
public void testGetVariantURIAlphabetizesWithMultipleVaryingHeaders() {
final String theURI = "theURI";
final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
when(mockEntry.hasVariants()).thenReturn(true);
when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
verify(mockEntry).headerIterator("Vary");
verify(mockRequest).getHeaders("Accept-Encoding");
verify(mockRequest).getHeaders("User-Agent");
Assertions.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
}
@Test
public void testGetVariantURIHandlesMultipleVaryHeaders() {
final String theURI = "theURI";
final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent"),
new BasicHeader("Vary", "Accept-Encoding") };
final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
when(mockEntry.hasVariants()).thenReturn(true);
when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
verify(mockEntry).headerIterator("Vary");
verify(mockRequest).getHeaders("Accept-Encoding");
verify(mockRequest).getHeaders("User-Agent");
Assertions.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
}
@Test
public void testGetVariantURIHandlesMultipleLineRequestHeaders() {
final String theURI = "theURI";
final Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
final Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip"),
new BasicHeader("Accept-Encoding", "deflate") };
final Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
extractor = new CacheKeyGenerator() {
@Override
public String generateKey(final HttpHost h, final HttpRequest request) {
Assertions.assertSame(defaultHost, h);
Assertions.assertSame(mockRequest, request);
return theURI;
}
};
when(mockEntry.hasVariants()).thenReturn(true);
when(mockEntry.headerIterator("Vary")).thenReturn(new BasicHeaderIterator(varyHeaders, "Vary"));
when(mockRequest.getHeaders("Accept-Encoding")).thenReturn(encHeaders);
when(mockRequest.getHeaders("User-Agent")).thenReturn(uaHeaders);
final String result = extractor.generateKey(defaultHost, mockRequest, mockEntry);
verify(mockEntry).hasVariants();
verify(mockEntry).headerIterator("Vary");
verify(mockRequest).getHeaders("Accept-Encoding");
verify(mockRequest).getHeaders("User-Agent");
Assertions.assertEquals("{Accept-Encoding=gzip%2C+deflate&User-Agent=browser}" + theURI, result);
} }
/* /*
@ -417,4 +249,66 @@ public class TestCacheKeyGenerator {
final HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/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(extractor.generateKey(host, req1), extractor.generateKey(host, req2));
} }
@Test
public void testGetURIWithQueryParameters() {
Assertions.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.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(
new HttpHost("http", "www.fancast.com", -1), new BasicHttpRequest("GET",
"/full_episodes?foo=bar")));
}
@Test
public void testGetVariantKey() {
final HttpRequest request = BasicRequestBuilder.get("/blah")
.addHeader(HttpHeaders.USER_AGENT, "some-agent")
.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip,zip")
.addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate")
.build();
Assertions.assertEquals("{user-agent=some-agent}",
extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
Assertions.assertEquals("{accept-encoding=deflate,gzip,zip}",
extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.ACCEPT_ENCODING)));
Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING)));
}
@Test
public void testGetVariantKeyInputNormalization() {
final HttpRequest request = BasicRequestBuilder.get("/blah")
.addHeader(HttpHeaders.USER_AGENT, "Some-Agent")
.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, ZIP,,")
.addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate")
.build();
Assertions.assertEquals("{user-agent=some-agent}",
extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
Assertions.assertEquals("{accept-encoding=deflate,gzip,zip}",
extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.ACCEPT_ENCODING)));
Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING)));
Assertions.assertEquals("{accept-encoding=deflate,gzip,zip&user-agent=some-agent}",
extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.USER_AGENT, HttpHeaders.ACCEPT_ENCODING, "USER-AGENT", HttpHeaders.ACCEPT_ENCODING)));
}
@Test
public void testGetVariantKeyInputNormalizationReservedChars() {
final HttpRequest request = BasicRequestBuilder.get("/blah")
.addHeader(HttpHeaders.USER_AGENT, "*===some-agent===*")
.build();
Assertions.assertEquals("{user-agent=%2A%3D%3D%3Dsome-agent%3D%3D%3D%2A}",
extractor.generateVariantKey(request, Collections.singletonList(HttpHeaders.USER_AGENT)));
}
@Test
public void testGetVariantKeyInputNoMatchingHeaders() {
final HttpRequest request = BasicRequestBuilder.get("/blah")
.build();
Assertions.assertEquals("{accept-encoding=&user-agent=}",
extractor.generateVariantKey(request, Arrays.asList(HttpHeaders.ACCEPT_ENCODING, HttpHeaders.USER_AGENT)));
}
} }