HTTPCLIENT-2284: Cache entry representation improvements: (#477)

* request URI stored in cache is now normalized and is presently expected to be the same as the root key
* removed references to variant cache keys from the cache entry
* Variants in root entries are now represented as a set, not as a map
This commit is contained in:
Oleg Kalnichevski 2023-08-22 11:13:26 +02:00
parent 5fe89b4e32
commit 097c17b78b
19 changed files with 225 additions and 206 deletions

View File

@ -28,11 +28,15 @@ package org.apache.hc.client5.http.cache;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
@ -69,7 +73,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
private final int status; private final int status;
private final HeaderGroup responseHeaders; private final HeaderGroup responseHeaders;
private final Resource resource; private final Resource resource;
private final Map<String, String> variantMap; private final Set<String> variants;
/** /**
* Internal constructor that makes no validation of the input parameters and makes * Internal constructor that makes no validation of the input parameters and makes
@ -85,7 +89,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
final int status, final int status,
final HeaderGroup responseHeaders, final HeaderGroup responseHeaders,
final Resource resource, final Resource resource,
final Map<String, String> variantMap) { final Collection<String> variants) {
super(); super();
this.requestDate = requestDate; this.requestDate = requestDate;
this.responseDate = responseDate; this.responseDate = responseDate;
@ -95,7 +99,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.status = status; this.status = status;
this.responseHeaders = responseHeaders; this.responseHeaders = responseHeaders;
this.resource = resource; this.resource = resource;
this.variantMap = variantMap != null ? new HashMap<>(variantMap) : null; this.variants = variants != null ? Collections.unmodifiableSet(new HashSet<>(variants)) : null;
} }
/** /**
@ -164,7 +168,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.responseHeaders = new HeaderGroup(); this.responseHeaders = new HeaderGroup();
this.responseHeaders.setHeaders(responseHeaders); this.responseHeaders.setHeaders(responseHeaders);
this.resource = resource; this.resource = resource;
this.variantMap = variantMap != null ? new HashMap<>(variantMap) : null; this.variants = variantMap != null ? Collections.unmodifiableSet(new HashSet<>(variantMap.keySet())) : null;
} }
/** /**
@ -353,30 +357,27 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* Indicates whether the origin response indicated the associated * Indicates whether the origin response indicated the associated
* resource had variants (i.e. that the Vary header was set on the * resource had variants (i.e. that the Vary header was set on the
* origin response). * origin response).
* @return {@code true} if this cached response was a variant
*/ */
public boolean hasVariants() { public boolean hasVariants() {
return containsHeader(HttpHeaders.VARY); return variants != null;
} }
/** /**
* Returns all known variants.
*
* @since 5.3 * @since 5.3
*/ */
public boolean isVariantRoot() { public Set<String> getVariants() {
return variantMap != null; return variants != null ? variants : Collections.emptySet();
} }
/** /**
* Returns an index about where in the cache different variants for * @deprecated No longer applicable. Use {@link #getVariants()} instead.
* a given resource are stored. This maps "variant keys" to "cache keys",
* where the variant key is derived from the varying request headers,
* and the cache key is the location in the
* {@link HttpCacheStorage} where that
* particular variant is stored. The first variant returned is used as
* the "parent" entry to hold this index of the other variants.
*/ */
@Deprecated
public Map<String, String> getVariantMap() { public Map<String, String> getVariantMap() {
return variantMap != null ? Collections.unmodifiableMap(variantMap) : Collections.emptyMap(); return variants != null ? variants.stream()
.collect(Collectors.toMap(e -> e, e -> e + requestURI)) : Collections.emptyMap();
} }
/** /**
@ -418,7 +419,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
", status=" + status + ", status=" + status +
", responseHeaders=" + responseHeaders + ", responseHeaders=" + responseHeaders +
", resource=" + resource + ", resource=" + resource +
", variantMap=" + variantMap + ", variants=" + variants +
'}'; '}';
} }
} }

View File

@ -26,15 +26,17 @@
*/ */
package org.apache.hc.client5.http.cache; package org.apache.hc.client5.http.cache;
import java.net.URI;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import org.apache.hc.client5.http.impl.cache.CacheSupport;
import org.apache.hc.client5.http.impl.cache.DateSupport; import org.apache.hc.client5.http.impl.cache.DateSupport;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
@ -43,6 +45,7 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HeaderElements;
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.HttpMessage; import org.apache.hc.core5.http.HttpMessage;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
@ -161,26 +164,36 @@ public class HttpCacheEntryFactory {
} }
} }
static String normalizeRequestUri(final HttpHost host, final HttpRequest request) {
final String s = CacheSupport.getRequestUri(request, host);
final URI normalizeRequestUri = CacheSupport.normalize(s);
return normalizeRequestUri.toASCIIString();
}
/** /**
* Creates a new root {@link HttpCacheEntry} (parent of multiple variants). * Creates a new root {@link HttpCacheEntry} (parent of multiple variants).
* *
* @param requestInstant Date/time when the request was made (Used for age calculations) * @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 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 request Original client request (a deep copy of this object is made)
* @param variantMap describing cache entries that are variants of this parent entry; this * @param variants describing cache entries that are variants of this parent entry; this
* maps a "variant key" (derived from the varying request headers) to a * maps a "variant key" (derived from the varying request headers) to a
* "cache key" (where in the cache storage the particular variant is * "cache key" (where in the cache storage the particular variant is
* located) * located)
*/ */
public HttpCacheEntry createRoot(final Instant requestInstant, public HttpCacheEntry createRoot(final Instant requestInstant,
final Instant responseInstant, final Instant responseInstant,
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse response, final HttpResponse response,
final Map<String, String> variantMap) { final Collection<String> variants) {
Args.notNull(requestInstant, "Request instant"); Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant"); Args.notNull(responseInstant, "Response instant");
Args.notNull(host, "Host");
Args.notNull(request, "Request"); Args.notNull(request, "Request");
Args.notNull(response, "Origin response"); Args.notNull(response, "Origin response");
final String requestUri = normalizeRequestUri(host, request);
final HeaderGroup requestHeaders = filterHopByHopHeaders(request); final HeaderGroup requestHeaders = filterHopByHopHeaders(request);
final HeaderGroup responseHeaders = filterHopByHopHeaders(response); final HeaderGroup responseHeaders = filterHopByHopHeaders(response);
ensureDate(responseHeaders, responseInstant); ensureDate(responseHeaders, responseInstant);
@ -188,12 +201,12 @@ public class HttpCacheEntryFactory {
requestInstant, requestInstant,
responseInstant, responseInstant,
request.getMethod(), request.getMethod(),
request.getRequestUri(), requestUri,
requestHeaders, requestHeaders,
response.getCode(), response.getCode(),
responseHeaders, responseHeaders,
null, null,
variantMap); variants);
} }
/** /**
@ -201,19 +214,23 @@ public class HttpCacheEntryFactory {
* *
* @param requestInstant Date/time when the request was made (Used for age calculations) * @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 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 request Original client request (a deep copy of this object is made)
* @param response Origin response (a deep copy of this object is made) * @param response Origin response (a deep copy of this object is made)
* @param resource Resource representing origin response body * @param resource Resource representing origin response body
*/ */
public HttpCacheEntry create(final Instant requestInstant, public HttpCacheEntry create(final Instant requestInstant,
final Instant responseInstant, final Instant responseInstant,
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse response, final HttpResponse response,
final Resource resource) { final Resource resource) {
Args.notNull(requestInstant, "Request instant"); Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant"); Args.notNull(responseInstant, "Response instant");
Args.notNull(host, "Host");
Args.notNull(request, "Request"); Args.notNull(request, "Request");
Args.notNull(response, "Origin response"); Args.notNull(response, "Origin response");
final String requestUri = normalizeRequestUri(host, request);
final HeaderGroup requestHeaders = filterHopByHopHeaders(request); final HeaderGroup requestHeaders = filterHopByHopHeaders(request);
final HeaderGroup responseHeaders = filterHopByHopHeaders(response); final HeaderGroup responseHeaders = filterHopByHopHeaders(response);
ensureDate(responseHeaders, responseInstant); ensureDate(responseHeaders, responseInstant);
@ -221,7 +238,7 @@ public class HttpCacheEntryFactory {
requestInstant, requestInstant,
responseInstant, responseInstant,
request.getMethod(), request.getMethod(),
request.getRequestUri(), requestUri,
requestHeaders, requestHeaders,
response.getCode(), response.getCode(),
responseHeaders, responseHeaders,
@ -282,7 +299,7 @@ public class HttpCacheEntryFactory {
entry.getStatus(), entry.getStatus(),
headers(entry.headerIterator()), headers(entry.headerIterator()),
entry.getResource(), entry.getResource(),
entry.isVariantRoot() ? new HashMap<>(entry.getVariantMap()) : null); entry.hasVariants() ? new HashSet<>(entry.getVariants()) : null);
} }
} }

View File

@ -703,6 +703,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
recordCacheUpdate(scope.clientContext); recordCacheUpdate(scope.clientContext);
operation.setDependency(responseCache.update( operation.setDependency(responseCache.update(
hit, hit,
target,
request, request,
backendResponse, backendResponse,
requestDate, requestDate,
@ -880,7 +881,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
triggerResponse(cacheResponse, scope, asyncExecCallback); triggerResponse(cacheResponse, scope, asyncExecCallback);
} }
if (partialMatch != null && partialMatch.entry.isVariantRoot()) { if (partialMatch != null && partialMatch.entry.hasVariants()) {
operation.setDependency(responseCache.getVariants( operation.setDependency(responseCache.getVariants(
partialMatch, partialMatch,
new FutureCallback<Collection<CacheHit>>() { new FutureCallback<Collection<CacheHit>>() {
@ -1016,6 +1017,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
} }
responseCache.update( responseCache.update(
hit, hit,
target,
request, request,
backendResponse, backendResponse,
requestDate, requestDate,

View File

@ -30,7 +30,7 @@ import java.net.URI;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -104,14 +104,14 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@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.hasVariants()) {
final List<String> variantNames = CacheKeyGenerator.variantNames(root); final List<String> variantNames = CacheKeyGenerator.variantNames(root);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames); final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String cacheKey = root.getVariantMap().get(variantKey); if (root.getVariants().contains(variantKey)) {
if (LOG.isDebugEnabled()) { final String cacheKey = variantKey + rootKey;
LOG.debug("Get cache variant entry: {}", cacheKey); if (LOG.isDebugEnabled()) {
} LOG.debug("Get cache variant entry: {}", cacheKey);
if (cacheKey != null) { }
complexCancellable.setDependency(storage.getEntry( complexCancellable.setDependency(storage.getEntry(
cacheKey, cacheKey,
new FutureCallback<HttpCacheEntry>() { new FutureCallback<HttpCacheEntry>() {
@ -179,8 +179,11 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
final ComplexCancellable complexCancellable = new ComplexCancellable(); final ComplexCancellable complexCancellable = new ComplexCancellable();
final HttpCacheEntry root = hit.entry; final HttpCacheEntry root = hit.entry;
if (root != null && root.isVariantRoot()) { final String rootKey = hit.rootKey;
final Set<String> variantCacheKeys = root.getVariantMap().keySet(); if (root != null && root.hasVariants()) {
final List<String> variantCacheKeys = root.getVariants().stream()
.map(e -> e + rootKey)
.collect(Collectors.toList());
complexCancellable.setDependency(storage.getEntries( complexCancellable.setDependency(storage.getEntries(
variantCacheKeys, variantCacheKeys,
new FutureCallback<Map<String, HttpCacheEntry>>() { new FutureCallback<Map<String, HttpCacheEntry>>() {
@ -253,6 +256,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
Cancellable storeVariant( Cancellable storeVariant(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
@ -278,9 +282,9 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
storage.updateEntry(rootKey, storage.updateEntry(rootKey,
existing -> { existing -> {
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>(); final Set<String> variantMap = existing != null ? new HashSet<>(existing.getVariants()) : new HashSet<>();
variantMap.put(variantKey, variantCacheKey); variantMap.add(variantKey);
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap); return cacheEntryFactory.createRoot(requestSent, responseReceived, host, request, originResponse, variantMap);
}, },
new FutureCallback<Boolean>() { new FutureCallback<Boolean>() {
@ -333,6 +337,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
Cancellable store( Cancellable store(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
@ -340,8 +345,8 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
final String rootKey, final String rootKey,
final HttpCacheEntry entry, final HttpCacheEntry entry,
final FutureCallback<CacheHit> callback) { final FutureCallback<CacheHit> callback) {
if (entry.hasVariants()) { if (entry.containsHeader(HttpHeaders.VARY)) {
return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry, callback); return storeVariant(host, request, originResponse, requestSent, responseReceived, rootKey, entry, callback);
} else { } else {
return storeEntry(rootKey, entry, callback); return storeEntry(rootKey, entry, callback);
} }
@ -370,14 +375,16 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
final HttpCacheEntry backup = cacheEntryFactory.create( final HttpCacheEntry backup = cacheEntryFactory.create(
requestSent, requestSent,
responseReceived, responseReceived,
host,
request, request,
originResponse, originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null); content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
callback.completed(new CacheHit(rootKey, backup)); callback.completed(new CacheHit(rootKey, backup));
return Operations.nonCancellable(); return Operations.nonCancellable();
} }
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource); final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, originResponse, resource);
return store( return store(
host,
request, request,
originResponse, originResponse,
requestSent, requestSent,
@ -390,6 +397,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@Override @Override
public Cancellable update( public Cancellable update(
final CacheHit stale, final CacheHit stale,
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
@ -410,6 +418,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
} }
return store( return store(
host,
request, request,
originResponse, originResponse,
requestSent, requestSent,
@ -454,7 +463,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey); LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
} }
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry, callback); return store(host, request, originResponse, requestSent, responseReceived, rootKey, hit.entry, callback);
} }
private void evictEntry(final String cacheKey) { private void evictEntry(final String cacheKey) {
@ -487,12 +496,13 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
LOG.debug("Evicting root cache entry {}", rootKey); LOG.debug("Evicting root cache entry {}", rootKey);
} }
evictEntry(rootKey); evictEntry(rootKey);
if (root.isVariantRoot()) { if (root.hasVariants()) {
for (final String variantKey : root.getVariantMap().values()) { for (final String variantKey : root.getVariants()) {
final String variantEntryKey = variantKey + rootKey;
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Evicting variant cache entry {}", variantKey); LOG.debug("Evicting variant cache entry {}", variantEntryKey);
} }
evictEntry(variantKey); evictEntry(variantEntryKey);
} }
} }
} }

View File

@ -30,10 +30,10 @@ import java.net.URI;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import org.apache.hc.client5.http.cache.HttpCacheCASOperation; import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
@ -150,14 +150,14 @@ class BasicHttpCache implements HttpCache {
if (root == null) { if (root == null) {
return null; return null;
} }
if (root.isVariantRoot()) { if (root.hasVariants()) {
final List<String> variantNames = CacheKeyGenerator.variantNames(root); final List<String> variantNames = CacheKeyGenerator.variantNames(root);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames); final String variantKey = cacheKeyGenerator.generateVariantKey(request, variantNames);
final String cacheKey = root.getVariantMap().get(variantKey); if (root.getVariants().contains(variantKey)) {
if (LOG.isDebugEnabled()) { final String cacheKey = variantKey + rootKey;
LOG.debug("Get cache variant entry: {}", cacheKey); if (LOG.isDebugEnabled()) {
} LOG.debug("Get cache variant entry: {}", cacheKey);
if (cacheKey != null) { }
final HttpCacheEntry entry = getInternal(cacheKey); final HttpCacheEntry entry = getInternal(cacheKey);
if (entry != null) { if (entry != null) {
return new CacheMatch(new CacheHit(rootKey, cacheKey, entry), new CacheHit(rootKey, root)); return new CacheMatch(new CacheHit(rootKey, cacheKey, entry), new CacheHit(rootKey, root));
@ -175,10 +175,11 @@ class BasicHttpCache implements HttpCache {
LOG.debug("Get variant cache entries: {}", hit.rootKey); LOG.debug("Get variant cache entries: {}", hit.rootKey);
} }
final HttpCacheEntry root = hit.entry; final HttpCacheEntry root = hit.entry;
if (root != null && root.isVariantRoot()) { final String rootKey = hit.rootKey;
if (root != null && root.hasVariants()) {
final List<CacheHit> variants = new ArrayList<>(); final List<CacheHit> variants = new ArrayList<>();
for (final String variantKey : root.getVariantMap().values()) { for (final String variantKey : root.getVariants()) {
final HttpCacheEntry variant = getInternal(variantKey); final HttpCacheEntry variant = getInternal(variantKey + rootKey);
if (variant != null) { if (variant != null) {
variants.add(new CacheHit(hit.rootKey, variantKey, variant)); variants.add(new CacheHit(hit.rootKey, variantKey, variant));
} }
@ -189,14 +190,15 @@ class BasicHttpCache implements HttpCache {
} }
CacheHit store( CacheHit store(
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 rootKey, final String rootKey,
final HttpCacheEntry entry) { final HttpCacheEntry entry) {
if (entry.hasVariants()) { if (entry.containsHeader(HttpHeaders.VARY)) {
return storeVariant(request, originResponse, requestSent, responseReceived, rootKey, entry); return storeVariant(host, request, originResponse, requestSent, responseReceived, rootKey, entry);
} else { } else {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Put entry in cache: {}", rootKey); LOG.debug("Put entry in cache: {}", rootKey);
@ -207,6 +209,7 @@ class BasicHttpCache implements HttpCache {
} }
CacheHit storeVariant( CacheHit storeVariant(
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
@ -228,9 +231,9 @@ class BasicHttpCache implements HttpCache {
} }
updateInternal(rootKey, existing -> { updateInternal(rootKey, existing -> {
final Map<String, String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>(); final Set<String> variants = existing != null ? new HashSet<>(existing.getVariants()) : new HashSet<>();
variantMap.put(variantKey, variantCacheKey); variants.add(variantKey);
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap); return cacheEntryFactory.createRoot(requestSent, responseReceived, host, request, originResponse, variants);
}); });
return new CacheHit(rootKey, variantCacheKey, entry); return new CacheHit(rootKey, variantCacheKey, entry);
} }
@ -257,18 +260,20 @@ class BasicHttpCache implements HttpCache {
final HttpCacheEntry backup = cacheEntryFactory.create( final HttpCacheEntry backup = cacheEntryFactory.create(
requestSent, requestSent,
responseReceived, responseReceived,
host,
request, request,
originResponse, originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null); content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
return new CacheHit(rootKey, backup); return new CacheHit(rootKey, backup);
} }
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse, resource); final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, host, request, originResponse, resource);
return store(request, originResponse, requestSent, responseReceived, rootKey, entry); return store(host, request, originResponse, requestSent, responseReceived, rootKey, entry);
} }
@Override @Override
public CacheHit update( public CacheHit update(
final CacheHit stale, final CacheHit stale,
final HttpHost host,
final HttpRequest request, final HttpRequest request,
final HttpResponse originResponse, final HttpResponse originResponse,
final Instant requestSent, final Instant requestSent,
@ -282,7 +287,7 @@ class BasicHttpCache implements HttpCache {
responseReceived, responseReceived,
originResponse, originResponse,
stale.entry); stale.entry);
return store(request, originResponse, requestSent, responseReceived, entryKey, updatedEntry); return store(host, request, originResponse, requestSent, responseReceived, entryKey, updatedEntry);
} }
@Override @Override
@ -321,7 +326,7 @@ class BasicHttpCache implements HttpCache {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey); LOG.debug("Store cache entry using existing entry: {} -> {}", rootKey, hit.rootKey);
} }
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry); return store(host, request, originResponse, requestSent, responseReceived, rootKey, hit.entry);
} }
private void evictAll(final HttpCacheEntry root, final String rootKey) { private void evictAll(final HttpCacheEntry root, final String rootKey) {
@ -329,12 +334,13 @@ class BasicHttpCache implements HttpCache {
LOG.debug("Evicting root cache entry {}", rootKey); LOG.debug("Evicting root cache entry {}", rootKey);
} }
removeInternal(rootKey); removeInternal(rootKey);
if (root.isVariantRoot()) { if (root.hasVariants()) {
for (final String variantKey : root.getVariantMap().values()) { for (final String variantKey : root.getVariants()) {
final String variantEntryKey = variantKey + rootKey;
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Evicting variant cache entry {}", variantKey); LOG.debug("Evicting variant cache entry {}", variantEntryKey);
} }
removeInternal(variantKey); removeInternal(variantEntryKey);
} }
} }
} }

View File

@ -339,7 +339,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
} }
if (statusCode == HttpStatus.SC_NOT_MODIFIED) { if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
final CacheHit updated = responseCache.update(hit, request, backendResponse, requestDate, responseDate); final CacheHit updated = responseCache.update(hit, target, request, backendResponse, requestDate, responseDate);
if (suitabilityChecker.isConditional(request) if (suitabilityChecker.isConditional(request)
&& suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) { && suitabilityChecker.allConditionalsMatch(request, updated.entry, Instant.now())) {
return convert(responseGenerator.generateNotModifiedResponse(updated.entry), scope); return convert(responseGenerator.generateNotModifiedResponse(updated.entry), scope);
@ -399,6 +399,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (hit != null) { if (hit != null) {
final CacheHit updated = responseCache.update( final CacheHit updated = responseCache.update(
hit, hit,
target,
request, request,
backendResponse, backendResponse,
requestSent, requestSent,
@ -458,7 +459,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (!mayCallBackend(requestCacheControl)) { if (!mayCallBackend(requestCacheControl)) {
return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"); return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
} }
if (partialMatch != null && partialMatch.entry.isVariantRoot()) { if (partialMatch != null && partialMatch.entry.hasVariants()) {
final List<CacheHit> variants = responseCache.getVariants(partialMatch); final List<CacheHit> variants = responseCache.getVariants(partialMatch);
if (variants != null && !variants.isEmpty()) { if (variants != null && !variants.isEmpty()) {
return negotiateResponseFromVariants(target, request, scope, chain, variants); return negotiateResponseFromVariants(target, request, scope, chain, variants);

View File

@ -69,6 +69,7 @@ interface HttpAsyncCache {
*/ */
Cancellable update( Cancellable update(
CacheHit stale, CacheHit stale,
HttpHost host,
HttpRequest request, HttpRequest request,
HttpResponse originResponse, HttpResponse originResponse,
Instant requestSent, Instant requestSent,

View File

@ -32,9 +32,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Set;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
@ -88,6 +88,7 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ
static final String HC_CACHE_LENGTH = "HC-Resource-Length"; static final String HC_CACHE_LENGTH = "HC-Resource-Length";
static final String HC_REQUEST_INSTANT = "HC-Request-Instant"; static final String HC_REQUEST_INSTANT = "HC-Request-Instant";
static final String HC_RESPONSE_INSTANT = "HC-Response-Instant"; static final String HC_RESPONSE_INSTANT = "HC-Response-Instant";
static final String HC_VARIANT = "HC-Variant";
/** /**
* Singleton instance of this class. * Singleton instance of this class.
@ -166,12 +167,11 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ
line.append(asStr(cacheEntry.getResponseInstant())); line.append(asStr(cacheEntry.getResponseInstant()));
outputBuffer.writeLine(line, out); outputBuffer.writeLine(line, out);
final Map<String, String> variantMap = cacheEntry.getVariantMap(); for (final String variant : cacheEntry.getVariants()) {
for (final Map.Entry<String, String> entry : variantMap.entrySet()) {
line.clear(); line.clear();
line.append(entry.getKey()); line.append(HC_VARIANT);
line.append(": "); line.append(": ");
line.append(entry.getValue()); line.append(variant);
outputBuffer.writeLine(line, out); outputBuffer.writeLine(line, out);
} }
line.clear(); line.clear();
@ -243,7 +243,7 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ
long length = -1; long length = -1;
Instant requestDate = null; Instant requestDate = null;
Instant responseDate = null; Instant responseDate = null;
final Map<String, String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
while (true) { while (true) {
line.clear(); line.clear();
@ -262,8 +262,10 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ
requestDate = asInstant(value); requestDate = asInstant(value);
} else if (name.equalsIgnoreCase(HC_RESPONSE_INSTANT)) { } else if (name.equalsIgnoreCase(HC_RESPONSE_INSTANT)) {
responseDate = asInstant(value); responseDate = asInstant(value);
} else if (name.equalsIgnoreCase(HC_VARIANT)) {
variants.add(value);
} else { } else {
variantMap.put(name, value); throw new ResourceIOException("Unexpected header entry");
} }
} }
@ -328,7 +330,7 @@ public class HttpByteArrayCacheEntrySerializer implements HttpCacheEntrySerializ
statusLine.getStatusCode(), statusLine.getStatusCode(),
responseHeaders, responseHeaders,
resource, resource,
variantMap !variants.isEmpty() ? variants : null
); );
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {

View File

@ -65,6 +65,7 @@ interface HttpCache {
*/ */
CacheHit update( CacheHit update(
CacheHit stale, CacheHit stale,
HttpHost host,
HttpRequest request, HttpRequest request,
HttpResponse originResponse, HttpResponse originResponse,
Instant requestSent, Instant requestSent,

View File

@ -27,19 +27,20 @@
package org.apache.hc.client5.http.cache; package org.apache.hc.client5.http.cache;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashSet;
import java.util.Map; import java.util.Set;
import org.apache.hc.client5.http.impl.cache.HttpTestUtils; import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
@ -47,6 +48,7 @@ import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method; import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.BasicHeader;
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;
@ -77,10 +79,10 @@ public class TestHttpCacheEntry {
final int status, final int status,
final Header[] headers, final Header[] headers,
final Resource resource, final Resource resource,
final Map<String, String> variantMap) { final Collection<String> variants) {
return new HttpCacheEntry(requestDate, responseDate, return new HttpCacheEntry(requestDate, responseDate,
"GET", "/", HttpTestUtils.headers(), "GET", "/", HttpTestUtils.headers(),
status, HttpTestUtils.headers(headers), resource, variantMap); status, HttpTestUtils.headers(headers), resource, variants);
} }
@Test @Test
public void testGetHeadersReturnsCorrectHeaders() { public void testGetHeadersReturnsCorrectHeaders() {
@ -122,29 +124,6 @@ public class TestHttpCacheEntry {
assertNull(entry.getFirstHeader("quux")); assertNull(entry.getFirstHeader("quux"));
} }
@Test
public void testCacheEntryWithOneVaryHeaderHasVariants() {
final Header[] headers = { new BasicHeader("Vary", "User-Agent") };
entry = makeEntry(headers);
assertTrue(entry.hasVariants());
}
@Test
public void testCacheEntryWithMultipleVaryHeadersHasVariants() {
final Header[] headers = { new BasicHeader("Vary", "User-Agent"),
new BasicHeader("Vary", "Accept-Encoding")
};
entry = makeEntry(headers);
assertTrue(entry.hasVariants());
}
@Test
public void testCacheEntryWithVaryStarHasVariants(){
final Header[] headers = { new BasicHeader("Vary", "*") };
entry = makeEntry(headers);
assertTrue(entry.hasVariants());
}
@Test @Test
public void testGetMethodReturnsCorrectRequestMethod() { public void testGetMethodReturnsCorrectRequestMethod() {
final Header[] headers = { new BasicHeader("foo", "fooValue"), final Header[] headers = { new BasicHeader("foo", "fooValue"),
@ -204,42 +183,38 @@ public class TestHttpCacheEntry {
public void canProvideVariantMap() { public void canProvideVariantMap() {
makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new Header[]{}, mockResource, new Header[]{}, mockResource,
new HashMap<>()); null);
} }
@Test @Test
public void canRetrieveOriginalVariantMap() { public void canRetrieveOriginalVariantMap() {
final Map<String,String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("A","B"); variants.add("A");
variantMap.put("C","D"); variants.add("B");
variants.add("C");
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new Header[]{}, mockResource, new Header[]{}, mockResource,
variantMap); variants);
final Map<String,String> result = entry.getVariantMap(); final Set<String> result = entry.getVariants();
assertEquals(2, result.size()); assertEquals(3, result.size());
assertEquals("B", result.get("A")); assertTrue(result.contains("A"));
assertEquals("D", result.get("C")); assertTrue(result.contains("B"));
assertTrue(result.contains("C"));
assertFalse(result.contains("D"));
} }
@Test @Test
public void retrievedVariantMapIsNotModifiable() { public void retrievedVariantMapIsNotModifiable() {
final Map<String,String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("A","B"); variants.add("A");
variantMap.put("C","D"); variants.add("B");
variants.add("C");
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new Header[]{}, mockResource, new Header[]{}, mockResource,
variantMap); variants);
final Map<String,String> result = entry.getVariantMap(); final Set<String> result = entry.getVariants();
try { Assertions.assertThrows(UnsupportedOperationException.class, () -> result.remove("A"));
result.remove("A"); Assertions.assertThrows(UnsupportedOperationException.class, () -> result.add("D"));
fail("Should have thrown exception");
} catch (final UnsupportedOperationException expected) {
}
try {
result.put("E","F");
fail("Should have thrown exception");
} catch (final UnsupportedOperationException expected) {
}
} }
@Test @Test

View File

@ -27,8 +27,7 @@
package org.apache.hc.client5.http.cache; package org.apache.hc.client5.http.cache;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.hc.client5.http.HeadersMatcher; import org.apache.hc.client5.http.HeadersMatcher;
@ -39,6 +38,7 @@ import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
@ -64,6 +64,7 @@ public class TestHttpCacheEntryFactory {
private Instant twoSecondsAgo; private Instant twoSecondsAgo;
private Instant eightSecondsAgo; private Instant eightSecondsAgo;
private Instant tenSecondsAgo; private Instant tenSecondsAgo;
private HttpHost host;
private HttpRequest request; private HttpRequest request;
private HttpResponse response; private HttpResponse response;
private HttpCacheEntryFactory impl; private HttpCacheEntryFactory impl;
@ -79,6 +80,7 @@ public class TestHttpCacheEntryFactory {
eightSecondsAgo = now.minusSeconds(8); eightSecondsAgo = now.minusSeconds(8);
tenSecondsAgo = now.minusSeconds(10); tenSecondsAgo = now.minusSeconds(10);
host = new HttpHost("foo.example.com");
request = new BasicHttpRequest("GET", "/stuff"); request = new BasicHttpRequest("GET", "/stuff");
response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified"); response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
@ -231,19 +233,19 @@ public class TestHttpCacheEntryFactory {
new BasicHeader("X-custom", "my stuff") new BasicHeader("X-custom", "my stuff")
); );
final Map<String, String> variants = new HashMap<>(); final Set<String> variants = new HashSet<>();
variants.put("key1", "variant1"); variants.add("variant1");
variants.put("key2", "variant2"); variants.add("variant2");
variants.put("key3", "variant3"); variants.add("variant3");
final HttpCacheEntry newEntry = impl.createRoot(tenSecondsAgo, oneSecondAgo, request, response, variants); final HttpCacheEntry newEntry = impl.createRoot(tenSecondsAgo, oneSecondAgo, host, request, response, variants);
MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent( MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent(
HttpTestUtils.makeCacheEntry( HttpTestUtils.makeCacheEntry(
tenSecondsAgo, tenSecondsAgo,
oneSecondAgo, oneSecondAgo,
Method.GET, Method.GET,
"/stuff", "http://foo.example.com:80/stuff",
new Header[]{ new Header[]{
new BasicHeader("X-custom", "my stuff"), new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"), new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
@ -259,7 +261,6 @@ public class TestHttpCacheEntryFactory {
variants variants
))); )));
Assertions.assertTrue(newEntry.isVariantRoot());
Assertions.assertTrue(newEntry.hasVariants()); Assertions.assertTrue(newEntry.hasVariants());
Assertions.assertNull(newEntry.getResource()); Assertions.assertNull(newEntry.getResource());
} }
@ -282,14 +283,14 @@ public class TestHttpCacheEntryFactory {
); );
final Resource resource = HttpTestUtils.makeRandomResource(128); final Resource resource = HttpTestUtils.makeRandomResource(128);
final HttpCacheEntry newEntry = impl.create(tenSecondsAgo, oneSecondAgo, request, response, resource); final HttpCacheEntry newEntry = impl.create(tenSecondsAgo, oneSecondAgo, host, request, response, resource);
MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent( MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent(
HttpTestUtils.makeCacheEntry( HttpTestUtils.makeCacheEntry(
tenSecondsAgo, tenSecondsAgo,
oneSecondAgo, oneSecondAgo,
Method.GET, Method.GET,
"/stuff", "http://foo.example.com:80/stuff",
new Header[]{ new Header[]{
new BasicHeader("X-custom", "my stuff"), new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"), new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
@ -304,7 +305,6 @@ public class TestHttpCacheEntryFactory {
resource resource
))); )));
Assertions.assertFalse(newEntry.isVariantRoot());
Assertions.assertFalse(newEntry.hasVariants()); Assertions.assertFalse(newEntry.hasVariants());
} }
@ -361,7 +361,6 @@ public class TestHttpCacheEntryFactory {
resource resource
))); )));
Assertions.assertFalse(updatedEntry.isVariantRoot());
Assertions.assertFalse(updatedEntry.hasVariants()); Assertions.assertFalse(updatedEntry.hasVariants());
} }

View File

@ -30,8 +30,8 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.Resource; import org.apache.hc.client5.http.cache.Resource;
@ -55,7 +55,7 @@ public class HttpCacheEntryMatcher extends BaseMatcher<HttpCacheEntry> {
try { try {
final HttpCacheEntry otherValue = (HttpCacheEntry) item; final HttpCacheEntry otherValue = (HttpCacheEntry) item;
if (!mapEqual(expectedValue.getVariantMap(), otherValue.getVariantMap())) { if (!setEqual(expectedValue.getVariants(), otherValue.getVariants())) {
return false; return false;
} }
if (!Objects.equals(expectedValue.getRequestMethod(), otherValue.getRequestMethod())) { if (!Objects.equals(expectedValue.getRequestMethod(), otherValue.getRequestMethod())) {
@ -114,12 +114,11 @@ public class HttpCacheEntryMatcher extends BaseMatcher<HttpCacheEntry> {
return true; return true;
} }
private static boolean mapEqual(final Map<?, ?> expected, final Map<?, ?> actual) { private static boolean setEqual(final Set<?> expected, final Set<?> actual) {
if (expected.size() != actual.size()) { if (expected.size() != actual.size()) {
return false; return false;
} }
return expected.entrySet().stream() return actual.containsAll(expected);
.allMatch(e -> Objects.equals(e.getValue(), actual.get(e.getKey())));
} }
@Override @Override

View File

@ -29,7 +29,7 @@ package org.apache.hc.client5.http.impl.cache;
import java.io.InputStream; import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -229,14 +229,14 @@ public class HttpTestUtils {
final Header[] requestHeaders, final Header[] requestHeaders,
final int status, final int status,
final Header[] responseHeaders, final Header[] responseHeaders,
final Map<String, String> variantMap) { final Collection<String> variants) {
return new HttpCacheEntry( return new HttpCacheEntry(
requestDate, requestDate,
responseDate, responseDate,
method.name(), requestUri, headers(requestHeaders), method.name(), requestUri, headers(requestHeaders),
status, headers(responseHeaders), status, headers(responseHeaders),
null, null,
variantMap); variants);
} }
public static HttpCacheEntry makeCacheEntry(final Instant requestDate, public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
@ -260,13 +260,13 @@ public class HttpTestUtils {
final Instant responseDate, final Instant responseDate,
final int status, final int status,
final Header[] responseHeaders, final Header[] responseHeaders,
final Map<String, String> variantMap) { final Collection<String> variants) {
return makeCacheEntry( return makeCacheEntry(
requestDate, requestDate,
responseDate, responseDate,
Method.GET, "/", null, Method.GET, "/", null,
status, responseHeaders, status, responseHeaders,
variantMap); variants);
} }
public static HttpCacheEntry makeCacheEntry(final Instant requestDate, public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
@ -311,13 +311,13 @@ public class HttpTestUtils {
public static HttpCacheEntry makeCacheEntry(final Instant requestDate, public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate, final Instant responseDate,
final Header[] headers, final Header[] headers,
final Map<String,String> variantMap) { final Collection<String> variants) {
return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variantMap); return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variants);
} }
public static HttpCacheEntry makeCacheEntry(final Map<String, String> variantMap) { public static HttpCacheEntry makeCacheEntry(final Collection<String> variants) {
final Instant now = Instant.now(); final Instant now = Instant.now();
return makeCacheEntry(now, now, new Header[] {}, variantMap); return makeCacheEntry(now, now, new Header[] {}, variants);
} }
public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) { public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {

View File

@ -31,8 +31,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.net.URI; import java.net.URI;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashSet;
import java.util.Map; import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
@ -111,15 +111,15 @@ public class TestBasicHttpAsyncCache {
public void testInvalidatesUnsafeRequestsWithVariants() throws Exception { public void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
final HttpRequest request = new BasicHttpRequest("POST", "/path"); final HttpRequest request = new BasicHttpRequest("POST", "/path");
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request); final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
final Set<String> variants = new HashSet<>();
variants.add("{var1}");
variants.add("{var2}");
final String variantKey1 = "{var1}" + rootKey; final String variantKey1 = "{var1}" + rootKey;
final String variantKey2 = "{var2}" + rootKey; final String variantKey2 = "{var2}" + rootKey;
final Map<String, String> variantMap = new HashMap<>();
variantMap.put("{var1}", variantKey1);
variantMap.put("{var2}", variantKey2);
final HttpResponse response = HttpTestUtils.make200Response(); final HttpResponse response = HttpTestUtils.make200Response();
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variantMap)); mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variants));
mockStorage.putEntry(variantKey1, HttpTestUtils.makeCacheEntry()); mockStorage.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
mockStorage.putEntry(variantKey2, HttpTestUtils.makeCacheEntry()); mockStorage.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());

View File

@ -37,9 +37,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.net.URI; import java.net.URI;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
@ -88,7 +89,7 @@ public class TestBasicHttpCache {
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req); final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
impl.store(req, resp, now, now, key, entry); impl.store(host, req, resp, now, now, key, entry);
assertSame(entry, backing.map.get(key)); assertSame(entry, backing.map.get(key));
} }
@ -211,10 +212,10 @@ public class TestBasicHttpCache {
@Test @Test
public void testGetVariantsRootNonExistentVariants() throws Exception { public void testGetVariantsRootNonExistentVariants() throws Exception {
final Map<String, String> variantMap = new HashMap<>(); final Set<String> varinats = new HashSet<>();
variantMap.put("variant1", "variant-key-1"); varinats.add("variant1");
variantMap.put("variant2", "variant-key-2"); varinats.add("variant2");
final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(variantMap); final HttpCacheEntry root = HttpTestUtils.makeCacheEntry(varinats);
final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root)); final List<CacheHit> variants = impl.getVariants(new CacheHit("root-key", root));
assertNotNull(variants); assertNotNull(variants);
@ -224,18 +225,20 @@ public class TestBasicHttpCache {
@Test @Test
public void testGetVariantCacheEntriesReturnsAllVariants() throws Exception { public void testGetVariantCacheEntriesReturnsAllVariants() throws Exception {
final HttpHost host = new HttpHost("foo.example.com"); final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest req1 = new HttpGet("http://foo.example.com/bar"); final URI uri = new URI("http://foo.example.com/bar");
final HttpRequest req1 = new HttpGet(uri);
req1.setHeader("Accept-Encoding", "gzip"); req1.setHeader("Accept-Encoding", "gzip");
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(uri);
final HttpResponse resp1 = HttpTestUtils.make200Response(); final HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now)); resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-Control", "max-age=3600, public"); resp1.setHeader("Cache-Control", "max-age=3600, public");
resp1.setHeader("ETag", "\"etag1\""); resp1.setHeader("ETag", "\"etag1\"");
resp1.setHeader("Vary", "Accept-Encoding"); resp1.setHeader("Vary", "Accept-Encoding");
resp1.setHeader("Content-Encoding","gzip"); resp1.setHeader("Content-Encoding","gzip");
resp1.setHeader("Vary", "Accept-Encoding");
final HttpRequest req2 = new HttpGet("http://foo.example.com/bar"); final HttpRequest req2 = new HttpGet(uri);
req2.setHeader("Accept-Encoding", "identity"); req2.setHeader("Accept-Encoding", "identity");
final HttpResponse resp2 = HttpTestUtils.make200Response(); final HttpResponse resp2 = HttpTestUtils.make200Response();
@ -244,23 +247,22 @@ public class TestBasicHttpCache {
resp2.setHeader("ETag", "\"etag2\""); resp2.setHeader("ETag", "\"etag2\"");
resp2.setHeader("Vary", "Accept-Encoding"); resp2.setHeader("Vary", "Accept-Encoding");
resp2.setHeader("Content-Encoding","gzip"); resp2.setHeader("Content-Encoding","gzip");
resp2.setHeader("Vary", "Accept-Encoding");
final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now); final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
final CacheHit hit2 = impl.store(host, req2, resp2, null, now, now); final CacheHit hit2 = impl.store(host, req2, resp2, null, now, now);
final Map<String, String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("variant-1", hit1.variantKey); variants.add("{accept-encoding=gzip}");
variantMap.put("variant-2", hit2.variantKey); variants.add("{accept-encoding=identity}");
final Map<String, HttpCacheEntry> variants = impl.getVariants(new CacheHit(hit1.rootKey, final Map<String, HttpCacheEntry> variantMap = impl.getVariants(new CacheHit(hit1.rootKey,
HttpTestUtils.makeCacheEntry(variantMap))).stream() HttpTestUtils.makeCacheEntry(variants))).stream()
.collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry)); .collect(Collectors.toMap(CacheHit::getEntryKey, e -> e.entry));
assertNotNull(variants); assertNotNull(variantMap);
assertEquals(2, variants.size()); assertEquals(2, variantMap.size());
MatcherAssert.assertThat(variants.get(hit1.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit1.entry)); MatcherAssert.assertThat(variantMap.get("{accept-encoding=gzip}"), HttpCacheEntryMatcher.equivalent(hit1.entry));
MatcherAssert.assertThat(variants.get(hit2.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit2.entry)); MatcherAssert.assertThat(variantMap.get("{accept-encoding=identity}"), HttpCacheEntryMatcher.equivalent(hit2.entry));
} }
@Test @Test
@ -300,15 +302,15 @@ public class TestBasicHttpCache {
public void testInvalidatesUnsafeRequestsWithVariants() throws Exception { public void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
final HttpRequest request = new BasicHttpRequest("POST","/path"); final HttpRequest request = new BasicHttpRequest("POST","/path");
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request); final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
final Set<String> variants = new HashSet<>();
variants.add("{var1}");
variants.add("{var2}");
final String variantKey1 = "{var1}" + rootKey; final String variantKey1 = "{var1}" + rootKey;
final String variantKey2 = "{var2}" + rootKey; final String variantKey2 = "{var2}" + rootKey;
final Map<String, String> variantMap = new HashMap<>();
variantMap.put("{var1}", variantKey1);
variantMap.put("{var2}", variantKey2);
final HttpResponse response = HttpTestUtils.make200Response(); final HttpResponse response = HttpTestUtils.make200Response();
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variantMap)); backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variants));
backing.putEntry(variantKey1, HttpTestUtils.makeCacheEntry()); backing.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
backing.putEntry(variantKey2, HttpTestUtils.makeCacheEntry()); backing.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());

View File

@ -38,8 +38,8 @@ import java.io.ObjectOutputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.Instant; import java.time.Instant;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashSet;
import java.util.Map; import java.util.Set;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry; import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
@ -274,15 +274,15 @@ public class TestByteArrayCacheEntrySerializer {
for (int i = 0; i < headers.length; i++) { for (int i = 0; i < headers.length; i++) {
headers[i] = new BasicHeader("header" + i, "value" + i); headers[i] = new BasicHeader("header" + i, "value" + i);
} }
final Map<String,String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("test variant 1","true"); variants.add("test variant 1");
variantMap.put("test variant 2","true"); variants.add("test variant 2");
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry( final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(
Instant.now(), Instant.now(),
Instant.now(), Instant.now(),
HttpStatus.SC_OK, HttpStatus.SC_OK,
headers, headers,
variantMap); variants);
return new HttpCacheStorageEntry(key, cacheEntry); return new HttpCacheStorageEntry(key, cacheEntry);
} }
@ -292,15 +292,15 @@ public class TestByteArrayCacheEntrySerializer {
for (int i = 0; i < headers.length; i++) { for (int i = 0; i < headers.length; i++) {
headers[i] = new BasicHeader("header" + i, "value" + i); headers[i] = new BasicHeader("header" + i, "value" + i);
} }
final Map<String,String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("test variant 1","true"); variants.add("test variant 1");
variantMap.put("test variant 2","true"); variants.add("test variant 2");
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry( final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(
Instant.now(), Instant.now(),
Instant.now(), Instant.now(),
HttpStatus.SC_OK, HttpStatus.SC_OK,
headers, headers,
variantMap); variants);
return new HttpCacheStorageEntry(key, cacheEntry); return new HttpCacheStorageEntry(key, cacheEntry);
} }

View File

@ -1432,7 +1432,7 @@ public class TestCachingExecChain {
headers, headers,
new HeapResource(body.getBytes(StandardCharsets.UTF_8))); new HeapResource(body.getBytes(StandardCharsets.UTF_8)));
Mockito.when(mockCache.update(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) Mockito.when(mockCache.update(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.thenReturn(new CacheHit("key", cacheEntry)); .thenReturn(new CacheHit("key", cacheEntry));
// Call cacheAndReturnResponse with 304 Not Modified response // Call cacheAndReturnResponse with 304 Not Modified response
@ -1441,6 +1441,7 @@ public class TestCachingExecChain {
// Verify cache entry is updated // Verify cache entry is updated
Mockito.verify(mockCache).update( Mockito.verify(mockCache).update(
Mockito.any(), Mockito.any(),
Mockito.same(host),
Mockito.same(request), Mockito.same(request),
Mockito.same(backendResponse), Mockito.same(backendResponse),
Mockito.eq(requestSent), Mockito.eq(requestSent),

View File

@ -32,8 +32,8 @@ import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashSet;
import java.util.Map; import java.util.Set;
import org.apache.hc.client5.http.cache.HttpCacheEntry; import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer; import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
@ -378,14 +378,14 @@ public class TestHttpByteArrayCacheEntrySerializer {
public void testSimpleVariantMap() throws Exception { public void testSimpleVariantMap() throws Exception {
final String content = "Hello World"; final String content = "Hello World";
final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8); final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8);
final Map<String, String> variantMap = new HashMap<>(); final Set<String> variants = new HashSet<>();
variantMap.put("{Accept-Encoding=gzip}","{Accept-Encoding=gzip}https://example.com:1234/foo"); variants.add("{Accept-Encoding=gzip}");
variantMap.put("{Accept-Encoding=compress}","{Accept-Encoding=compress}https://example.com:1234/foo"); variants.add("{Accept-Encoding=compress}");
final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(), final HttpCacheEntry cacheEntry = new HttpCacheEntry(Instant.now(), Instant.now(),
"GET", "/stuff", HttpTestUtils.headers(), "GET", "/stuff", HttpTestUtils.headers(),
HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())), HttpStatus.SC_OK, HttpTestUtils.headers(new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString())),
new HeapResource(content.getBytes(contentType.getCharset())), new HeapResource(content.getBytes(contentType.getCharset())),
variantMap); variants);
final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry); final HttpCacheStorageEntry storageEntry = new HttpCacheStorageEntry("unique-cache-key", cacheEntry);
final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry); final byte[] serialized = httpCacheEntrySerializer.serialize(storageEntry);

View File

@ -1723,6 +1723,7 @@ public class TestProtocolRequirements {
Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(),
Mockito.any(), Mockito.any(),
Mockito.any(),
Mockito.any())) Mockito.any()))
.thenReturn(new CacheHit("key", HttpTestUtils.makeCacheEntry())); .thenReturn(new CacheHit("key", HttpTestUtils.makeCacheEntry()));
@ -1730,6 +1731,7 @@ public class TestProtocolRequirements {
Mockito.verify(mockCache).update( Mockito.verify(mockCache).update(
Mockito.any(), Mockito.any(),
Mockito.eq(host),
RequestEquivalent.eq(request), RequestEquivalent.eq(request),
ResponseEquivalent.eq(notModified), ResponseEquivalent.eq(notModified),
Mockito.any(), Mockito.any(),