HTTPCLIENT-2277, HTTPCLIENT-1347: Revision of the variant handling by the HTTP cache implementations

* Cache entries now can be of two distinct types: root entries containing a map of known representation variants of the same resource and resource entries containing a resource potentially sharable by multiple resource entries. The same entry cannot have a variant map and a resource at the same time
* Cache entry factory class added to the public APIs
This commit is contained in:
Oleg Kalnichevski 2023-06-18 17:09:22 +02:00
parent 1d9d6d70c9
commit 6f1fd6d26b
25 changed files with 1106 additions and 1085 deletions

View File

@ -1,93 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.cache;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.message.MessageSupport;
@Internal
public final class CacheHeaderSupport {
private final static Set<String> HOP_BY_HOP;
static {
final TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set.add(HttpHeaders.CONNECTION);
set.add(HttpHeaders.CONTENT_LENGTH);
set.add(HttpHeaders.TRANSFER_ENCODING);
set.add(HttpHeaders.HOST);
set.add(HttpHeaders.KEEP_ALIVE);
set.add(HttpHeaders.TE);
set.add(HttpHeaders.UPGRADE);
set.add(HttpHeaders.PROXY_AUTHORIZATION);
set.add("Proxy-Authentication-Info");
set.add(HttpHeaders.PROXY_AUTHENTICATE);
HOP_BY_HOP = Collections.unmodifiableSet(set);
}
public static boolean isHopByHop(final String headerName) {
if (headerName == null) {
return false;
}
return HOP_BY_HOP.contains(headerName);
}
public static boolean isHopByHop(final Header header) {
if (header == null) {
return false;
}
return isHopByHop(header.getName());
}
/**
* This method should be provided by the core
*/
public static Set<String> hopByHopConnectionSpecific(final MessageHeaders headers) {
final Header connectionHeader = headers.getFirstHeader(HttpHeaders.CONNECTION);
final String connDirective = connectionHeader != null ? connectionHeader.getValue() : null;
// Disregard most common 'Close' and 'Keep-Alive' tokens
if (connDirective != null &&
!connDirective.equalsIgnoreCase(HeaderElements.CLOSE) &&
!connDirective.equalsIgnoreCase(HeaderElements.KEEP_ALIVE)) {
final TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
result.addAll(HOP_BY_HOP);
result.addAll(MessageSupport.parseTokens(connectionHeader));
return result;
} else {
return HOP_BY_HOP;
}
}
}

View File

@ -32,9 +32,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract;
@ -42,8 +40,6 @@ import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.Method;
@ -75,61 +71,6 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
private final Resource resource;
private final Map<String, String> variantMap;
/**
* Create a new {@link HttpCacheEntry}.
*
* @param requestDate Date/time when the request was made (Used for age calculations)
* @param responseDate Date/time that the response came back (Used for age calculations)
* @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 resource Resource representing origin response body
* @param variantMap describing cache entries that are variants of this parent entry; this
* maps a "variant key" (derived from the varying request headers) to a
* "cache key" (where in the cache storage the particular variant is
* located)
* @since 5.3
*/
public static HttpCacheEntry create(final Instant requestDate,
final Instant responseDate,
final HttpRequest request,
final HttpResponse response,
final Resource resource,
final Map<String, String> variantMap) {
Args.notNull(requestDate, "Request date");
Args.notNull(responseDate, "Response date");
Args.notNull(request, "Request");
Args.notNull(response, "Origin response");
final Set<String> requestHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(request);
final HeaderGroup requestHeaders = new HeaderGroup();
for (final Iterator<Header> it = request.headerIterator(); it.hasNext(); ) {
final Header header = it.next();
if (!requestHopByHop.contains(header.getName().toLowerCase(Locale.ROOT))) {
requestHeaders.addHeader(header);
}
}
final Set<String> responseHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(request);
final HeaderGroup responseHeaders = new HeaderGroup();
for (final Iterator<Header> it = response.headerIterator(); it.hasNext(); ) {
final Header header = it.next();
if (!responseHopByHop.contains(header.getName().toLowerCase(Locale.ROOT))) {
responseHeaders.addHeader(header);
}
}
return new HttpCacheEntry(
requestDate,
responseDate,
request.getMethod(),
request.getRequestUri(),
requestHeaders,
response.getCode(),
responseHeaders,
resource,
variantMap);
}
/**
* Internal constructor that makes no validation of the input parameters and makes
* no copies of the original client request and the origin response.
@ -174,7 +115,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* of this parent entry; this maps a "variant key" (derived
* from the varying request headers) to a "cache key" (where
* in the cache storage the particular variant is located)
* @deprecated Use {{@link HttpCacheEntry#create(Instant, Instant, HttpRequest, HttpResponse, Resource, Map)}
* @deprecated Use {{@link HttpCacheEntryFactory}
*/
@Deprecated
public HttpCacheEntry(
@ -199,7 +140,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* maps a "variant key" (derived from the varying request headers) to a
* "cache key" (where in the cache storage the particular variant is
* located)
* @deprecated Use {{@link HttpCacheEntry#create(Instant, Instant, HttpRequest, HttpResponse, Resource, Map)}
* @deprecated Use {{@link HttpCacheEntryFactory}
*/
@Deprecated
public HttpCacheEntry(
@ -234,7 +175,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* @param status HTTP status from origin response
* @param responseHeaders Header[] from original HTTP Response
* @param resource representing origin response body
* @deprecated Use {{@link HttpCacheEntry#create(Instant, Instant, HttpRequest, HttpResponse, Resource, Map)}
* @deprecated Use {{@link HttpCacheEntryFactory}
*/
@Deprecated
public HttpCacheEntry(final Date requestDate, final Date responseDate, final int status,
@ -257,7 +198,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* Header[] from original HTTP Response
* @param resource representing origin response body
*
* @deprecated Use {{@link HttpCacheEntry#create(Instant, Instant, HttpRequest, HttpResponse, Resource, Map)}
* @deprecated Use {{@link HttpCacheEntryFactory}
*/
@Deprecated
public HttpCacheEntry(final Instant requestDate, final Instant responseDate, final int status,
@ -415,7 +356,14 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
* @return {@code true} if this cached response was a variant
*/
public boolean hasVariants() {
return getFirstHeader(HttpHeaders.VARY) != null;
return containsHeader(HttpHeaders.VARY);
}
/**
* @since 5.3
*/
public boolean isVariantRoot() {
return variantMap != null;
}
/**

View File

@ -0,0 +1,287 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.cache;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.hc.client5.http.impl.cache.DateSupport;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpMessage;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.message.HeaderGroup;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.util.Args;
/**
* {@link HttpCacheEntry} factory.
*
* @since 5.3
*/
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class HttpCacheEntryFactory {
public static final HttpCacheEntryFactory INSTANCE = new HttpCacheEntryFactory();
private final static Set<String> HOP_BY_HOP;
static {
final TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set.add(HttpHeaders.CONNECTION);
set.add(HttpHeaders.CONTENT_LENGTH);
set.add(HttpHeaders.TRANSFER_ENCODING);
set.add(HttpHeaders.HOST);
set.add(HttpHeaders.KEEP_ALIVE);
set.add(HttpHeaders.TE);
set.add(HttpHeaders.UPGRADE);
set.add(HttpHeaders.PROXY_AUTHORIZATION);
set.add("Proxy-Authentication-Info");
set.add(HttpHeaders.PROXY_AUTHENTICATE);
HOP_BY_HOP = Collections.unmodifiableSet(set);
}
@Internal
public static boolean isHopByHop(final String headerName) {
if (headerName == null) {
return false;
}
return HOP_BY_HOP.contains(headerName);
}
@Internal
public static boolean isHopByHop(final Header header) {
if (header == null) {
return false;
}
return isHopByHop(header.getName());
}
private static HeaderGroup headers(final Iterator<Header> it) {
final HeaderGroup headerGroup = new HeaderGroup();
while (it.hasNext()) {
headerGroup.addHeader(it.next());
}
return headerGroup;
}
HeaderGroup mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) {
final HeaderGroup headerGroup = new HeaderGroup();
for (final Iterator<Header> it = entry.headerIterator(); it.hasNext(); ) {
final Header entryHeader = it.next();
final String headerName = entryHeader.getName();
// Since we do not expect a content in a 304 response, should retain the original Content-Encoding header
if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING)) {
headerGroup.addHeader(entryHeader);
} else if (headerName.equalsIgnoreCase(HttpHeaders.WARNING)) {
// remove cache entry 1xx warnings
final String warningValue = entryHeader.getValue();
if (warningValue != null && !warningValue.startsWith("1")) {
headerGroup.addHeader(entryHeader);
}
} else {
if (!response.containsHeader(headerName)) {
headerGroup.addHeader(entryHeader);
}
}
}
final Set<String> responseHopByHop = HttpCacheEntryFactory.hopByHopConnectionSpecific(response);
for (final Iterator<Header> it = response.headerIterator(); it.hasNext(); ) {
final Header responseHeader = it.next();
final String headerName = responseHeader.getName();
// Since we do not expect a content in a 304 response, should update the cache entry with Content-Encoding
if (!headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING) &&
!responseHopByHop.contains(headerName.toLowerCase(Locale.ROOT))) {
headerGroup.addHeader(responseHeader);
}
}
return headerGroup;
}
/**
* This method should be provided by the core
*/
static Set<String> hopByHopConnectionSpecific(final MessageHeaders headers) {
final Header connectionHeader = headers.getFirstHeader(HttpHeaders.CONNECTION);
final String connDirective = connectionHeader != null ? connectionHeader.getValue() : null;
// Disregard most common 'Close' and 'Keep-Alive' tokens
if (connDirective != null &&
!connDirective.equalsIgnoreCase(HeaderElements.CLOSE) &&
!connDirective.equalsIgnoreCase(HeaderElements.KEEP_ALIVE)) {
final TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
result.addAll(HOP_BY_HOP);
result.addAll(MessageSupport.parseTokens(connectionHeader));
return result;
} else {
return HOP_BY_HOP;
}
}
static HeaderGroup filterHopByHopHeaders(final HttpMessage message) {
final Set<String> hopByHop = hopByHopConnectionSpecific(message);
final HeaderGroup headerGroup = new HeaderGroup();
for (final Iterator<Header> it = message.headerIterator(); it.hasNext(); ) {
final Header header = it.next();
if (!hopByHop.contains(header.getName())) {
headerGroup.addHeader(header);
}
}
return headerGroup;
}
/**
* Creates a new root {@link HttpCacheEntry} (parent of multiple variants).
*
* @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 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
* maps a "variant key" (derived from the varying request headers) to a
* "cache key" (where in the cache storage the particular variant is
* located)
*/
public HttpCacheEntry createRoot(final Instant requestInstant,
final Instant responseInstant,
final HttpRequest request,
final HttpResponse response,
final Map<String, String> variantMap) {
Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant");
Args.notNull(request, "Request");
Args.notNull(response, "Origin response");
return new HttpCacheEntry(
requestInstant,
responseInstant,
request.getMethod(),
request.getRequestUri(),
filterHopByHopHeaders(request),
response.getCode(),
filterHopByHopHeaders(response),
null,
variantMap);
}
/**
* Create a new {@link HttpCacheEntry} with the given {@link Resource}.
*
* @param requestInstant Date/time when the request was made (Used for age calculations)
* @param responseInstant Date/time that the response came back (Used for age calculations)
* @param 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 resource Resource representing origin response body
*/
public HttpCacheEntry create(final Instant requestInstant,
final Instant responseInstant,
final HttpRequest request,
final HttpResponse response,
final Resource resource) {
Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant");
Args.notNull(request, "Request");
Args.notNull(response, "Origin response");
return new HttpCacheEntry(
requestInstant,
responseInstant,
request.getMethod(),
request.getRequestUri(),
filterHopByHopHeaders(request),
response.getCode(),
filterHopByHopHeaders(response),
resource,
null);
}
/**
* Creates updated entry with the new information from the response. Should only be used for
* 304 responses.
*
* @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 response Origin response (a deep copy of this object is made)
* @param entry Existing cache entry.
*/
public HttpCacheEntry createUpdated(
final Instant requestInstant,
final Instant responseInstant,
final HttpResponse response,
final HttpCacheEntry entry) {
Args.notNull(requestInstant, "Request instant");
Args.notNull(responseInstant, "Response instant");
Args.notNull(response, "Origin response");
Args.check(response.getCode() == HttpStatus.SC_NOT_MODIFIED,
"Response must have 304 status code");
Args.notNull(entry, "Cache entry");
if (DateSupport.isAfter(entry, response, HttpHeaders.DATE)) {
return entry;
}
final HeaderGroup mergedHeaders = mergeHeaders(entry, response);
return new HttpCacheEntry(
requestInstant,
responseInstant,
entry.getRequestMethod(),
entry.getRequestURI(),
headers(entry.requestHeaderIterator()),
entry.getStatus(),
mergedHeaders,
entry.getResource(),
null);
}
/**
* Creates a copy of the given {@link HttpCacheEntry}. Please note the underlying
* {@link Resource} is copied by reference.
*/
public HttpCacheEntry copy(final HttpCacheEntry entry) {
if (entry == null) {
return null;
}
return new HttpCacheEntry(
entry.getRequestInstant(),
entry.getResponseInstant(),
entry.getRequestMethod(),
entry.getRequestURI(),
headers(entry.requestHeaderIterator()),
entry.getStatus(),
headers(entry.headerIterator()),
entry.getResource(),
entry.isVariantRoot() ? new HashMap<>(entry.getVariantMap()) : null);
}
}

View File

@ -530,7 +530,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate, final ByteArrayBuffer buffer) {
final CancellableDependency operation = scope.cancellableDependency;
operation.setDependency(responseCache.createCacheEntry(
operation.setDependency(responseCache.createEntry(
target,
request,
backendResponse,
@ -746,7 +746,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
void triggerUpdatedCacheEntryResponse(final HttpResponse backendResponse, final Instant responseDate) {
final CancellableDependency operation = scope.cancellableDependency;
recordCacheUpdate(scope.clientContext);
operation.setDependency(responseCache.updateCacheEntry(
operation.setDependency(responseCache.updateEntry(
target,
request,
cacheEntry,
@ -973,11 +973,12 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final Variant matchingVariant) {
recordCacheUpdate(scope.clientContext);
operation.setDependency(responseCache.updateVariantCacheEntry(
final HttpCacheEntry variantEntry = matchingVariant.getEntry();
operation.setDependency(responseCache.updateVariantEntry(
target,
conditionalRequest,
backendResponse,
matchingVariant,
variantEntry,
requestDate,
responseDate,
new FutureCallback<HttpCacheEntry>() {
@ -993,7 +994,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
operation.setDependency(responseCache.reuseVariantEntryFor(
target,
request,
matchingVariant,
backendResponse,
responseEntry,
requestDate,
responseDate,
new FutureCallback<Boolean>() {
@Override
@ -1048,7 +1052,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
if (LOG.isDebugEnabled()) {
LOG.debug("Existing cache entry found, updating cache entry");
}
responseCache.updateCacheEntry(
responseCache.updateEntry(
target,
request,
existingEntry,

View File

@ -34,6 +34,7 @@ import java.util.Set;
import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
@ -57,17 +58,20 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
private static final Logger LOG = LoggerFactory.getLogger(BasicHttpAsyncCache.class);
private final CacheUpdateHandler cacheUpdateHandler;
private final ResourceFactory resourceFactory;
private final HttpCacheEntryFactory cacheEntryFactory;
private final CacheKeyGenerator cacheKeyGenerator;
private final HttpAsyncCacheInvalidator cacheInvalidator;
private final HttpAsyncCacheStorage storage;
public BasicHttpAsyncCache(
final ResourceFactory resourceFactory,
final HttpCacheEntryFactory cacheEntryFactory,
final HttpAsyncCacheStorage storage,
final CacheKeyGenerator cacheKeyGenerator,
final HttpAsyncCacheInvalidator cacheInvalidator) {
this.cacheUpdateHandler = new CacheUpdateHandler(resourceFactory);
this.resourceFactory = resourceFactory;
this.cacheEntryFactory = cacheEntryFactory;
this.cacheKeyGenerator = cacheKeyGenerator;
this.storage = storage;
this.cacheInvalidator = cacheInvalidator;
@ -77,7 +81,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
final ResourceFactory resourceFactory,
final HttpAsyncCacheStorage storage,
final CacheKeyGenerator cacheKeyGenerator) {
this(resourceFactory, storage, cacheKeyGenerator, DefaultAsyncCacheInvalidator.INSTANCE);
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator, DefaultAsyncCacheInvalidator.INSTANCE);
}
public BasicHttpAsyncCache(final ResourceFactory resourceFactory, final HttpAsyncCacheStorage storage) {
@ -154,13 +158,16 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
}
Cancellable storeInCache(
final String cacheKey,
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final Instant requestSent,
final Instant responseReceived,
final String cacheKey,
final HttpCacheEntry entry,
final FutureCallback<Boolean> callback) {
if (entry.hasVariants()) {
return storeVariantEntry(cacheKey, host, request, entry, callback);
return storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry, callback);
} else {
return storeEntry(cacheKey, entry, callback);
}
@ -198,19 +205,26 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
}
Cancellable storeVariantEntry(
final String cacheKey,
final HttpHost host,
final HttpRequest req,
final HttpRequest request,
final HttpResponse originResponse,
final Instant requestSent,
final Instant responseReceived,
final String cacheKey,
final HttpCacheEntry entry,
final FutureCallback<Boolean> callback) {
final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, req, entry);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, request, entry);
return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
@Override
public void completed(final Boolean result) {
storage.updateEntry(cacheKey,
existing -> cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey),
existing -> {
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
variantMap.put(variantKey, variantCacheKey);
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
},
new FutureCallback<Boolean>() {
@Override
@ -263,48 +277,22 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@Override
public Cancellable reuseVariantEntryFor(
final HttpHost host, final HttpRequest request, final Variant variant, final FutureCallback<Boolean> callback) {
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final HttpCacheEntry entry,
final Instant requestSent,
final Instant responseReceived,
final FutureCallback<Boolean> callback) {
if (LOG.isDebugEnabled()) {
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), variant);
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry entry = variant.getEntry();
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
final String variantCacheKey = variant.getCacheKey();
return storage.updateEntry(cacheKey,
existing -> cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey),
new FutureCallback<Boolean>() {
@Override
public void completed(final Boolean result) {
callback.completed(result);
}
@Override
public void failed(final Exception ex) {
if (ex instanceof HttpCacheUpdateException) {
if (LOG.isWarnEnabled()) {
LOG.warn("Cannot update cache entry with key {}", cacheKey);
}
} else if (ex instanceof ResourceIOException) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
}
} else {
callback.failed(ex);
}
}
@Override
public void cancelled() {
callback.cancelled();
}
});
return storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry, callback);
}
@Override
public Cancellable updateCacheEntry(
public Cancellable updateEntry(
final HttpHost host,
final HttpRequest request,
final HttpCacheEntry stale,
@ -316,90 +304,79 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
try {
final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
request.getRequestUri(),
stale,
requestSent,
responseReceived,
originResponse);
return storeInCache(cacheKey, host, request, updatedEntry, new FutureCallback<Boolean>() {
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent,
responseReceived,
originResponse,
stale);
return storeInCache(
host,
request,
originResponse,
requestSent,
responseReceived,
cacheKey,
updatedEntry,
new FutureCallback<Boolean>() {
@Override
public void completed(final Boolean result) {
callback.completed(updatedEntry);
}
@Override
public void completed(final Boolean result) {
callback.completed(updatedEntry);
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void cancelled() {
callback.cancelled();
}
@Override
public void cancelled() {
callback.cancelled();
}
});
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
}
callback.completed(stale);
return Operations.nonCancellable();
}
});
}
@Override
public Cancellable updateVariantCacheEntry(
public Cancellable updateVariantEntry(
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final Variant variant,
final HttpCacheEntry entry,
final Instant requestSent,
final Instant responseReceived,
final FutureCallback<HttpCacheEntry> callback) {
if (LOG.isDebugEnabled()) {
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), variant);
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
}
final HttpCacheEntry entry = variant.getEntry();
final String cacheKey = variant.getCacheKey();
try {
final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
request.getRequestUri(),
entry,
requestSent,
responseReceived,
originResponse);
return storeEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() {
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent,
responseReceived,
originResponse,
entry);
return storeEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() {
@Override
public void completed(final Boolean result) {
callback.completed(updatedEntry);
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void cancelled() {
callback.cancelled();
}
});
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
@Override
public void completed(final Boolean result) {
callback.completed(updatedEntry);
}
callback.completed(entry);
return Operations.nonCancellable();
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void cancelled() {
callback.cancelled();
}
});
}
@Override
public Cancellable createCacheEntry(
public Cancellable createEntry(
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
@ -412,36 +389,43 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
try {
final HttpCacheEntry entry = cacheUpdateHandler.createCacheEntry(request, originResponse, content, requestSent, responseReceived);
return storeInCache(cacheKey, host, request, entry, new FutureCallback<Boolean>() {
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
return storeInCache(
host,
request,
originResponse,
requestSent,
responseReceived,
cacheKey,
entry, new FutureCallback<Boolean>() {
@Override
public void completed(final Boolean result) {
callback.completed(entry);
}
@Override
public void completed(final Boolean result) {
callback.completed(entry);
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void failed(final Exception ex) {
callback.failed(ex);
}
@Override
public void cancelled() {
callback.cancelled();
}
@Override
public void cancelled() {
callback.cancelled();
}
});
});
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error creating cache entry with key {}", cacheKey);
}
callback.completed(HttpCacheEntry.create(
callback.completed(cacheEntryFactory.create(
requestSent,
responseReceived,
request,
originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null,
null));
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null));
return Operations.nonCancellable();
}
}
@ -458,7 +442,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@Override
public void completed(final HttpCacheEntry root) {
if (root != null) {
if (root.hasVariants()) {
if (root.isVariantRoot()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
final String variantCacheKey = root.getVariantMap().get(variantKey);
if (variantCacheKey != null) {
@ -530,7 +514,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
@Override
public void completed(final HttpCacheEntry rootEntry) {
if (rootEntry != null && rootEntry.hasVariants()) {
if (rootEntry != null && rootEntry.isVariantRoot()) {
final Set<String> variantCacheKeys = rootEntry.getVariantMap().keySet();
complexCancellable.setDependency(storage.getEntries(
variantCacheKeys,

View File

@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
@ -52,17 +53,20 @@ class BasicHttpCache implements HttpCache {
private static final Logger LOG = LoggerFactory.getLogger(BasicHttpCache.class);
private final CacheUpdateHandler cacheUpdateHandler;
private final ResourceFactory resourceFactory;
private final HttpCacheEntryFactory cacheEntryFactory;
private final CacheKeyGenerator cacheKeyGenerator;
private final HttpCacheInvalidator cacheInvalidator;
private final HttpCacheStorage storage;
public BasicHttpCache(
final ResourceFactory resourceFactory,
final HttpCacheEntryFactory cacheEntryFactory,
final HttpCacheStorage storage,
final CacheKeyGenerator cacheKeyGenerator,
final HttpCacheInvalidator cacheInvalidator) {
this.cacheUpdateHandler = new CacheUpdateHandler(resourceFactory);
this.resourceFactory = resourceFactory;
this.cacheEntryFactory = cacheEntryFactory;
this.cacheKeyGenerator = cacheKeyGenerator;
this.storage = storage;
this.cacheInvalidator = cacheInvalidator;
@ -72,7 +76,7 @@ class BasicHttpCache implements HttpCache {
final ResourceFactory resourceFactory,
final HttpCacheStorage storage,
final CacheKeyGenerator cacheKeyGenerator) {
this(resourceFactory, storage, cacheKeyGenerator, new DefaultCacheInvalidator());
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator, new DefaultCacheInvalidator());
}
public BasicHttpCache(final ResourceFactory resourceFactory, final HttpCacheStorage storage) {
@ -132,12 +136,15 @@ class BasicHttpCache implements HttpCache {
}
void storeInCache(
final String cacheKey,
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final Instant requestSent,
final Instant responseReceived,
final String cacheKey,
final HttpCacheEntry entry) {
if (entry.hasVariants()) {
storeVariantEntry(cacheKey, host, request, entry);
storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry);
} else {
storeEntry(cacheKey, entry);
}
@ -154,15 +161,22 @@ class BasicHttpCache implements HttpCache {
}
void storeVariantEntry(
final String cacheKey,
final HttpHost host,
final HttpRequest req,
final HttpRequest request,
final HttpResponse originResponse,
final Instant requestSent,
final Instant responseReceived,
final String cacheKey,
final HttpCacheEntry entry) {
final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, req, entry);
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
final String variantCacheKey = cacheKeyGenerator.generateKey(host, request, entry);
storeEntry(variantCacheKey, entry);
try {
storage.updateEntry(cacheKey, existing -> cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey));
storage.updateEntry(cacheKey, existing -> {
final Map<String,String> variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>();
variantMap.put(variantKey, variantCacheKey);
return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap);
});
} catch (final HttpCacheUpdateException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("Cannot update cache entry with key {}", cacheKey);
@ -176,30 +190,21 @@ class BasicHttpCache implements HttpCache {
@Override
public void reuseVariantEntryFor(
final HttpHost host, final HttpRequest request, final Variant variant) {
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final HttpCacheEntry entry,
final Instant requestSent,
final Instant responseReceived) {
if (LOG.isDebugEnabled()) {
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), variant);
LOG.debug("Re-use variant entry: {}; {} / {}", host, new RequestLine(request), entry);
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry entry = variant.getEntry();
final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
final String variantCacheKey = variant.getCacheKey();
try {
storage.updateEntry(cacheKey, existing -> cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey));
} catch (final HttpCacheUpdateException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("Cannot update cache entry with key {}", cacheKey);
}
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
}
}
storeVariantEntry(host, request, originResponse, requestSent, responseReceived, cacheKey, entry);
}
@Override
public HttpCacheEntry updateCacheEntry(
public HttpCacheEntry updateEntry(
final HttpHost host,
final HttpRequest request,
final HttpCacheEntry stale,
@ -210,55 +215,38 @@ class BasicHttpCache implements HttpCache {
LOG.debug("Update cache entry: {}; {}", host, new RequestLine(request));
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
try {
final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
request.getRequestUri(),
stale,
requestSent,
responseReceived,
originResponse);
storeInCache(cacheKey, host, request, updatedEntry);
return updatedEntry;
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
}
return stale;
}
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent,
responseReceived,
originResponse,
stale);
storeInCache(host, request, originResponse, requestSent, responseReceived, cacheKey, updatedEntry);
return updatedEntry;
}
@Override
public HttpCacheEntry updateVariantCacheEntry(
public HttpCacheEntry updateVariantEntry(
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
final Variant variant,
final HttpCacheEntry entry,
final Instant requestSent,
final Instant responseReceived) {
if (LOG.isDebugEnabled()) {
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), variant);
}
final HttpCacheEntry entry = variant.getEntry();
final String cacheKey = variant.getCacheKey();
try {
final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
request.getRequestUri(),
entry,
requestSent,
responseReceived,
originResponse);
storeEntry(cacheKey, updatedEntry);
return updatedEntry;
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error updating cache entry with key {}", cacheKey);
}
return entry;
LOG.debug("Update variant cache entry: {}; {} / {}", host, new RequestLine(request), entry);
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated(
requestSent,
responseReceived,
originResponse,
entry);
storeEntry(cacheKey, updatedEntry);
return updatedEntry;
}
@Override
public HttpCacheEntry createCacheEntry(
public HttpCacheEntry createEntry(
final HttpHost host,
final HttpRequest request,
final HttpResponse originResponse,
@ -270,20 +258,20 @@ class BasicHttpCache implements HttpCache {
}
final String cacheKey = cacheKeyGenerator.generateKey(host, request);
try {
final HttpCacheEntry entry = cacheUpdateHandler.createCacheEntry(request, originResponse, content, requestSent, responseReceived);
storeInCache(cacheKey, host, request, entry);
final HttpCacheEntry entry = cacheEntryFactory.create(requestSent, responseReceived, request, originResponse,
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null);
storeInCache(host, request, originResponse, requestSent, responseReceived, cacheKey, entry);
return entry;
} catch (final ResourceIOException ex) {
if (LOG.isWarnEnabled()) {
LOG.warn("I/O error creating cache entry with key {}", cacheKey);
}
return HttpCacheEntry.create(
return cacheEntryFactory.create(
requestSent,
responseReceived,
request,
originResponse,
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null,
null);
content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
}
}
@ -305,7 +293,7 @@ class BasicHttpCache implements HttpCache {
if (root == null) {
return null;
}
if (root.hasVariants()) {
if (root.isVariantRoot()) {
final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
final String variantCacheKey = root.getVariantMap().get(variantKey);
if (variantCacheKey != null) {
@ -339,7 +327,7 @@ class BasicHttpCache implements HttpCache {
}
return variants;
}
if (root != null && root.hasVariants()) {
if (root != null && root.isVariantRoot()) {
for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
final String variantCacheKey = variant.getValue();
try {

View File

@ -1,187 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.time.Instant;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.hc.client5.http.cache.CacheHeaderSupport;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.message.HeaderGroup;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.ByteArrayBuffer;
/**
* Creates new {@link HttpCacheEntry}s and updates existing ones with new or updated information
* based on the response from the origin server.
*/
class CacheUpdateHandler {
private final ResourceFactory resourceFactory;
CacheUpdateHandler() {
this(new HeapResourceFactory());
}
CacheUpdateHandler(final ResourceFactory resourceFactory) {
super();
this.resourceFactory = resourceFactory;
}
/**
* Creates a cache entry for the given request, origin response message and response content.
*/
public HttpCacheEntry createCacheEntry(
final HttpRequest request,
final HttpResponse originResponse,
final ByteArrayBuffer content,
final Instant requestSent,
final Instant responseReceived) throws ResourceIOException {
return HttpCacheEntry.create(
requestSent,
responseReceived,
request,
originResponse,
content != null ? resourceFactory.generate(request.getRequestUri(), content.array(), 0, content.length()) : null,
null);
}
/**
* Update the entry with the new information from the response. Should only be used for
* 304 responses.
*/
public HttpCacheEntry updateCacheEntry(
final String requestId,
final HttpCacheEntry entry,
final Instant requestDate,
final Instant responseDate,
final HttpResponse response) throws ResourceIOException {
Args.check(response.getCode() == HttpStatus.SC_NOT_MODIFIED,
"Response must have 304 status code");
if (DateSupport.isAfter(entry, response, HttpHeaders.DATE)) {
return entry;
}
final HeaderGroup mergedHeaders = mergeHeaders(entry, response);
Resource resource = null;
if (entry.getResource() != null) {
resource = resourceFactory.copy(requestId, entry.getResource());
}
return new HttpCacheEntry(
requestDate,
responseDate,
entry.getRequestMethod(),
entry.getRequestURI(),
headers(entry.requestHeaderIterator()),
entry.getStatus(),
mergedHeaders,
resource,
null);
}
public HttpCacheEntry updateParentCacheEntry(
final String requestId,
final HttpCacheEntry existing,
final HttpCacheEntry entry,
final String variantKey,
final String variantCacheKey) throws ResourceIOException {
HttpCacheEntry src = existing;
if (src == null) {
src = entry;
}
Resource resource = null;
if (src.getResource() != null) {
resource = resourceFactory.copy(requestId, src.getResource());
}
final Map<String,String> variantMap = new HashMap<>(src.getVariantMap());
variantMap.put(variantKey, variantCacheKey);
return new HttpCacheEntry(
src.getRequestInstant(),
src.getResponseInstant(),
src.getRequestMethod(),
src.getRequestURI(),
headers(src.requestHeaderIterator()),
src.getStatus(),
headers(src.headerIterator()),
resource,
variantMap);
}
private static HeaderGroup headers(final Iterator<Header> it) {
final HeaderGroup headerGroup = new HeaderGroup();
while (it.hasNext()) {
headerGroup.addHeader(it.next());
}
return headerGroup;
}
private HeaderGroup mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) {
final HeaderGroup headerGroup = new HeaderGroup();
for (final Iterator<Header> it = entry.headerIterator(); it.hasNext(); ) {
final Header entryHeader = it.next();
final String headerName = entryHeader.getName();
// Since we do not expect a content in a 304 response, should retain the original Content-Encoding header
if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING)) {
headerGroup.addHeader(entryHeader);
} else if (headerName.equalsIgnoreCase(HttpHeaders.WARNING)) {
// remove cache entry 1xx warnings
final String warningValue = entryHeader.getValue();
if (warningValue != null && !warningValue.startsWith("1")) {
headerGroup.addHeader(entryHeader);
}
} else {
if (!response.containsHeader(headerName)) {
headerGroup.addHeader(entryHeader);
}
}
}
final Set<String> responseHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(response);
for (final Iterator<Header> it = response.headerIterator(); it.hasNext(); ) {
final Header responseHeader = it.next();
final String headerName = responseHeader.getName();
// Since we do not expect a content in a 304 response, should update the cache entry with Content-Encoding
if (!headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING) &&
!responseHopByHop.contains(headerName.toLowerCase(Locale.ROOT))) {
headerGroup.addHeader(responseHeader);
}
}
return headerGroup;
}
}

View File

@ -159,11 +159,6 @@ class CachedHttpResponseGenerator {
response.setHeader(HttpHeaders.CONTENT_LENGTH, Integer.toString(body.length));
}
private boolean transferEncodingIsPresent(final HttpResponse response) {
final Header hdr = response.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
return hdr != null;
}
private boolean responseShouldContainEntity(final HttpRequest request, final HttpCacheEntry cacheEntry) {
return request.getMethod().equals(HeaderConstants.GET_METHOD) && cacheEntry.getResource() != null;
}

View File

@ -39,7 +39,6 @@ import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
@ -58,7 +57,6 @@ import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
@ -102,46 +100,17 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
private final HttpCache responseCache;
private final DefaultCacheRevalidator cacheRevalidator;
private final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder;
private final CacheUpdateHandler cacheUpdateHandler;
private static final Logger LOG = LoggerFactory.getLogger(CachingExec.class);
CachingExec(final HttpCache cache, final DefaultCacheRevalidator cacheRevalidator, final CacheConfig config, final CacheUpdateHandler cacheUpdateHandler) {
CachingExec(final HttpCache cache, final DefaultCacheRevalidator cacheRevalidator, final CacheConfig config) {
super(config);
this.responseCache = Args.notNull(cache, "Response cache");
this.cacheRevalidator = cacheRevalidator;
this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(classicHttpRequest ->
ClassicRequestBuilder.copy(classicHttpRequest).build());
this.cacheUpdateHandler = cacheUpdateHandler != null ? cacheUpdateHandler: new CacheUpdateHandler();
}
CachingExec(final HttpCache cache, final DefaultCacheRevalidator cacheRevalidator, final CacheConfig config) {
this(cache, cacheRevalidator, config, null);
}
CachingExec(
final HttpCache responseCache,
final CacheValidityPolicy validityPolicy,
final ResponseCachingPolicy responseCachingPolicy,
final CachedHttpResponseGenerator responseGenerator,
final CacheableRequestPolicy cacheableRequestPolicy,
final CachedResponseSuitabilityChecker suitabilityChecker,
final ResponseProtocolCompliance responseCompliance,
final RequestProtocolCompliance requestCompliance,
final DefaultCacheRevalidator cacheRevalidator,
final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
final CacheConfig config,
final CacheUpdateHandler cacheUpdateHandler) {
super(validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
suitabilityChecker, responseCompliance, requestCompliance, config);
this.responseCache = responseCache;
this.cacheRevalidator = cacheRevalidator;
this.conditionalRequestBuilder = conditionalRequestBuilder;
this.cacheUpdateHandler = cacheUpdateHandler;
}
CachingExec(
final HttpCache responseCache,
final CacheValidityPolicy validityPolicy,
@ -154,14 +123,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final DefaultCacheRevalidator cacheRevalidator,
final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
final CacheConfig config) {
this(responseCache,validityPolicy,responseCachingPolicy,responseGenerator,cacheableRequestPolicy,
suitabilityChecker,
responseCompliance,
requestCompliance,
cacheRevalidator,
conditionalRequestBuilder,
config,
null);
super(validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
suitabilityChecker, responseCompliance, requestCompliance, config);
this.responseCache = responseCache;
this.cacheRevalidator = cacheRevalidator;
this.conditionalRequestBuilder = conditionalRequestBuilder;
}
CachingExec(
@ -171,16 +137,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final CacheConfig config) {
this(cache,
executorService != null ? new DefaultCacheRevalidator(executorService, schedulingStrategy) : null,
config, null);
}
CachingExec(
final ResourceFactory resourceFactory,
final HttpCacheStorage storage,
final ScheduledExecutorService executorService,
final SchedulingStrategy schedulingStrategy,
final CacheConfig config) {
this(new BasicHttpCache(resourceFactory, storage), executorService, schedulingStrategy, config);
config);
}
@Override
@ -390,7 +347,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
}
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
final HttpCacheEntry updatedEntry = responseCache.updateEntry(
target, request, cacheEntry, backendResponse, requestDate, responseDate);
if (suitabilityChecker.isConditional(request)
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, Instant.now())) {
@ -452,13 +409,13 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request);
if (existingEntry != null) {
final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
request.getMethod(),
final HttpCacheEntry updatedEntry = responseCache.updateEntry(
target,
request,
existingEntry,
backendResponse,
requestSent,
responseReceived,
backendResponse);
responseReceived);
return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
}
}
@ -492,11 +449,11 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
LOG.debug("Backend already contains fresher cache entry");
cacheEntry = existingEntry;
} else {
cacheEntry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived);
cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
LOG.debug("Backend response successfully cached");
}
} else {
cacheEntry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived);
cacheEntry = responseCache.createEntry(target, request, backendResponse, buf, requestSent, responseReceived);
LOG.debug("Backend response successfully cached (freshness check skipped)");
}
return convert(responseGenerator.generateResponse(request, cacheEntry), scope);
@ -539,13 +496,14 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
return handleBackendResponse(target, request, scope, requestDate, responseDate, backendResponse);
} else {
// 304 response are not expected to have an enclosed content body, but still
backendResponse.close();
}
final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
if (resultEtagHeader == null) {
LOG.warn("304 response did not contain ETag");
EntityUtils.consume(backendResponse.getEntity());
backendResponse.close();
return callBackend(target, request, scope, chain);
}
@ -553,29 +511,26 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final Variant matchingVariant = variants.get(resultEtag);
if (matchingVariant == null) {
LOG.debug("304 response did not contain ETag matching one sent in If-None-Match");
EntityUtils.consume(backendResponse.getEntity());
backendResponse.close();
return callBackend(target, request, scope, chain);
}
if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())
final HttpCacheEntry variantEntry = matchingVariant.getEntry();
if (revalidationResponseIsTooOld(backendResponse, variantEntry)
&& (request.getEntity() == null || request.getEntity().isRepeatable())) {
EntityUtils.consume(backendResponse.getEntity());
backendResponse.close();
final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
return callBackend(target, unconditional, scope, chain);
}
recordCacheUpdate(scope.clientContext);
final HttpCacheEntry responseEntry = responseCache.updateVariantCacheEntry(
target, conditionalRequest, backendResponse, matchingVariant, requestDate, responseDate);
backendResponse.close();
final HttpCacheEntry responseEntry = responseCache.updateVariantEntry(
target, conditionalRequest, backendResponse, variantEntry, requestDate, responseDate);
if (shouldSendNotModifiedResponse(request, responseEntry)) {
return convert(responseGenerator.generateNotModifiedResponse(responseEntry), scope);
}
final SimpleHttpResponse response = responseGenerator.generateResponse(request, responseEntry);
responseCache.reuseVariantEntryFor(target, request, matchingVariant);
responseCache.reuseVariantEntryFor(target, request, backendResponse, responseEntry, requestDate, responseDate);
return convert(response, scope);
} catch (final IOException | RuntimeException ex) {
backendResponse.close();

View File

@ -34,6 +34,7 @@ import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorageAdaptor;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.impl.ChainElement;
@ -137,6 +138,7 @@ public class CachingH2AsyncClientBuilder extends H2AsyncClientBuilder {
}
final HttpAsyncCache httpCache = new BasicHttpAsyncCache(
resourceFactoryCopy,
HttpCacheEntryFactory.INSTANCE,
storageCopy,
CacheKeyGenerator.INSTANCE,
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());

View File

@ -34,6 +34,7 @@ import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorageAdaptor;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.impl.ChainElement;
@ -137,6 +138,7 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
}
final HttpAsyncCache httpCache = new BasicHttpAsyncCache(
resourceFactoryCopy,
HttpCacheEntryFactory.INSTANCE,
storageCopy,
CacheKeyGenerator.INSTANCE,
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());

View File

@ -30,6 +30,7 @@ import java.io.File;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceFactory;
@ -129,6 +130,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
}
final HttpCache httpCache = new BasicHttpCache(
resourceFactoryCopy,
HttpCacheEntryFactory.INSTANCE,
storageCopy,
CacheKeyGenerator.INSTANCE,
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultCacheInvalidator());

View File

@ -74,7 +74,7 @@ interface HttpAsyncCache {
/**
* Store a {@link HttpResponse} in the cache if possible, and return
*/
Cancellable createCacheEntry(
Cancellable createEntry(
HttpHost host,
HttpRequest request,
HttpResponse originResponse,
@ -86,7 +86,7 @@ interface HttpAsyncCache {
/**
* Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
*/
Cancellable updateCacheEntry(
Cancellable updateEntry(
HttpHost host,
HttpRequest request,
HttpCacheEntry stale,
@ -99,11 +99,11 @@ interface HttpAsyncCache {
* Update a specific {@link HttpCacheEntry} representing a cached variant
* using a 304 {@link HttpResponse}.
*/
Cancellable updateVariantCacheEntry(
Cancellable updateVariantEntry(
HttpHost host,
HttpRequest request,
HttpResponse originResponse,
Variant variant,
HttpCacheEntry entry,
Instant requestSent,
Instant responseReceived,
FutureCallback<HttpCacheEntry> callback);
@ -114,7 +114,10 @@ interface HttpAsyncCache {
*/
Cancellable reuseVariantEntryFor(
HttpHost host,
HttpRequest req,
Variant variant,
HttpRequest request,
HttpResponse originResponse,
HttpCacheEntry entry,
Instant requestSent,
Instant responseReceived,
FutureCallback<Boolean> callback);
}

View File

@ -68,7 +68,7 @@ interface HttpCache {
/**
* Store a {@link HttpResponse} in the cache if possible, and return
*/
HttpCacheEntry createCacheEntry(
HttpCacheEntry createEntry(
HttpHost host,
HttpRequest request,
HttpResponse originResponse,
@ -79,7 +79,7 @@ interface HttpCache {
/**
* Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
*/
HttpCacheEntry updateCacheEntry(
HttpCacheEntry updateEntry(
HttpHost host,
HttpRequest request,
HttpCacheEntry stale,
@ -91,11 +91,11 @@ interface HttpCache {
* Update a specific {@link HttpCacheEntry} representing a cached variant
* using a 304 {@link HttpResponse}.
*/
HttpCacheEntry updateVariantCacheEntry(
HttpCacheEntry updateVariantEntry(
HttpHost host,
HttpRequest request,
HttpResponse originResponse,
Variant variant,
HttpCacheEntry entry,
Instant requestSent,
Instant responseReceived);
@ -106,5 +106,8 @@ interface HttpCache {
void reuseVariantEntryFor(
HttpHost host,
HttpRequest request,
Variant variant);
HttpResponse originResponse,
HttpCacheEntry entry,
Instant requestSent,
Instant responseReceived);
}

View File

@ -1,67 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.cache;
import java.util.Set;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.support.BasicResponseBuilder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestCacheHeaderSupport {
@Test
public void testHopByHopHeaders() {
Assertions.assertTrue(CacheHeaderSupport.isHopByHop("Connection"));
Assertions.assertTrue(CacheHeaderSupport.isHopByHop("connection"));
Assertions.assertTrue(CacheHeaderSupport.isHopByHop("coNNection"));
Assertions.assertFalse(CacheHeaderSupport.isHopByHop("Content-Type"));
Assertions.assertFalse(CacheHeaderSupport.isHopByHop("huh"));
}
@Test
public void testHopByHopHeadersConnectionSpecific() {
final HttpResponse response = BasicResponseBuilder.create(HttpStatus.SC_OK)
.addHeader(HttpHeaders.CONNECTION, "blah, blah, this, that")
.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_PLAIN.toString())
.build();
final Set<String> hopByHopConnectionSpecific = CacheHeaderSupport.hopByHopConnectionSpecific(response);
Assertions.assertTrue(hopByHopConnectionSpecific.contains("Connection"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("connection"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("coNNection"));
Assertions.assertFalse(hopByHopConnectionSpecific.contains("Content-Type"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("blah"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("Blah"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("This"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("That"));
}
}

View File

@ -0,0 +1,430 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.cache;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.hc.client5.http.HeadersMatcher;
import org.apache.hc.client5.http.impl.cache.ContainsHeaderMatcher;
import org.apache.hc.client5.http.impl.cache.HttpCacheEntryMatcher;
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.http.message.HeaderGroup;
import org.apache.hc.core5.http.support.BasicResponseBuilder;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class TestHttpCacheEntryFactory {
private Instant requestDate;
private Instant responseDate;
private HttpCacheEntry entry;
private Instant now;
private Instant oneSecondAgo;
private Instant twoSecondsAgo;
private Instant eightSecondsAgo;
private Instant tenSecondsAgo;
private HttpRequest request;
private HttpResponse response;
private HttpCacheEntryFactory impl;
@BeforeEach
public void setUp() throws Exception {
requestDate = Instant.now().minusSeconds(1);
responseDate = Instant.now();
now = Instant.now();
oneSecondAgo = now.minusSeconds(1);
twoSecondsAgo = now.minusSeconds(2);
eightSecondsAgo = now.minusSeconds(8);
tenSecondsAgo = now.minusSeconds(10);
request = new BasicHttpRequest("GET", "/stuff");
response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
impl = new HttpCacheEntryFactory();
}
@Test
public void testHopByHopHeaders() {
Assertions.assertTrue(HttpCacheEntryFactory.isHopByHop("Connection"));
Assertions.assertTrue(HttpCacheEntryFactory.isHopByHop("connection"));
Assertions.assertTrue(HttpCacheEntryFactory.isHopByHop("coNNection"));
Assertions.assertFalse(HttpCacheEntryFactory.isHopByHop("Content-Type"));
Assertions.assertFalse(HttpCacheEntryFactory.isHopByHop("huh"));
}
@Test
public void testHopByHopHeadersConnectionSpecific() {
final HttpResponse response = BasicResponseBuilder.create(HttpStatus.SC_OK)
.addHeader(HttpHeaders.CONNECTION, "blah, blah, this, that")
.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_PLAIN.toString())
.build();
final Set<String> hopByHopConnectionSpecific = HttpCacheEntryFactory.hopByHopConnectionSpecific(response);
Assertions.assertTrue(hopByHopConnectionSpecific.contains("Connection"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("connection"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("coNNection"));
Assertions.assertFalse(hopByHopConnectionSpecific.contains("Content-Type"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("blah"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("Blah"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("This"));
Assertions.assertTrue(hopByHopConnectionSpecific.contains("That"));
}
@Test
public void testFilterHopByHopAndConnectionSpecificHeaders() {
response.setHeaders(
new BasicHeader(HttpHeaders.CONNECTION, "blah, blah, this, that"),
new BasicHeader("Blah", "huh?"),
new BasicHeader("BLAH", "huh?"),
new BasicHeader("this", "huh?"),
new BasicHeader("That", "huh?"),
new BasicHeader("Keep-Alive", "timeout, max=20"),
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_PLAIN.toString()),
new BasicHeader(HttpHeaders.CONTENT_LENGTH, "111"));
final HeaderGroup filteredHeaders = HttpCacheEntryFactory.filterHopByHopHeaders(response);
MatcherAssert.assertThat(filteredHeaders.getHeaders(), HeadersMatcher.same(
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.TEXT_PLAIN.toString())
));
}
@Test
public void testHeadersAreMergedCorrectly() {
entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("ETag", "\"etag\""));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(responseDate)));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
}
@Test
public void testNewerHeadersReplaceExistingHeaders() {
entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("Cache-Control", "private"),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("Cache-Control", "max-age=0"));
response.setHeaders(
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(requestDate)));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatStandardDate(responseDate)));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Cache-Control", "public"));
}
@Test
public void testNewHeadersAreAddedByMerge() {
entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("ETag", "\"etag\""));
response.setHeaders(
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(requestDate)));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatStandardDate(responseDate)));
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Cache-Control", "public"));
}
@Test
public void entry1xxWarningsAreRemovedOnMerge() {
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
new BasicHeader("Warning", "110 fred \"Response is stale\""),
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", DateUtils.formatStandardDate(eightSecondsAgo)));
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
Assertions.assertEquals(0, mergedHeaders.countHeaders("Warning"));
}
@Test
public void entryWithMalformedDateIsStillUpdated() throws Exception {
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", "bad-date"));
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("ETag", "\"new\""));
}
@Test
public void entryIsStillUpdatedByResponseWithMalformedDate() throws Exception {
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)));
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", "bad-date");
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("ETag", "\"new\""));
}
@Test
public void testContentEncodingHeaderIsNotUpdatedByMerge() {
entry = HttpTestUtils.makeCacheEntry(
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Content-Encoding", "identity"));
response.setHeaders(
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"),
new BasicHeader("Content-Encoding", "gzip"));
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Content-Encoding", "identity"));
MatcherAssert.assertThat(mergedHeaders, Matchers.not(ContainsHeaderMatcher.contains("Content-Encoding", "gzip")));
}
@Test
public void testUpdateCacheEntryReturnsDifferentEntryInstance() {
entry = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry newEntry = impl.createUpdated(requestDate, responseDate, response, entry);
Assertions.assertNotSame(newEntry, entry);
}
@Test
public void testCreateRootVariantEntry() {
request.setHeaders(
new BasicHeader("Keep-Alive", "timeout, max=20"),
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
);
response.setHeaders(
new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "identity"),
new BasicHeader(HttpHeaders.CONNECTION, "Keep-Alive, Blah"),
new BasicHeader("Blah", "huh?"),
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader(HttpHeaders.VARY, "Stuff"),
new BasicHeader(HttpHeaders.ETAG, "\"some-etag\""),
new BasicHeader("X-custom", "my stuff")
);
final Map<String, String> variants = new HashMap<>();
variants.put("key1", "variant1");
variants.put("key2", "variant2");
variants.put("key3", "variant3");
final HttpCacheEntry newEntry = impl.createRoot(tenSecondsAgo, oneSecondAgo, request, response, variants);
MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent(
HttpTestUtils.makeCacheEntry(
tenSecondsAgo,
oneSecondAgo,
Method.GET,
"/stuff",
new Header[]{
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
},
HttpStatus.SC_NOT_MODIFIED,
new Header[]{
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader(HttpHeaders.VARY, "Stuff"),
new BasicHeader(HttpHeaders.ETAG, "\"some-etag\""),
new BasicHeader("X-custom", "my stuff")
},
variants
)));
Assertions.assertTrue(newEntry.isVariantRoot());
Assertions.assertTrue(newEntry.hasVariants());
Assertions.assertNull(newEntry.getResource());
}
@Test
public void testCreateResourceEntry() {
request.setHeaders(
new BasicHeader("Keep-Alive", "timeout, max=20"),
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
);
response.setHeaders(
new BasicHeader(HttpHeaders.TRANSFER_ENCODING, "identity"),
new BasicHeader(HttpHeaders.CONNECTION, "Keep-Alive, Blah"),
new BasicHeader("Blah", "huh?"),
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader(HttpHeaders.ETAG, "\"some-etag\""),
new BasicHeader("X-custom", "my stuff")
);
final Resource resource = HttpTestUtils.makeRandomResource(128);
final HttpCacheEntry newEntry = impl.create(tenSecondsAgo, oneSecondAgo, request, response, resource);
MatcherAssert.assertThat(newEntry, HttpCacheEntryMatcher.equivalent(
HttpTestUtils.makeCacheEntry(
tenSecondsAgo,
oneSecondAgo,
Method.GET,
"/stuff",
new Header[]{
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
},
HttpStatus.SC_NOT_MODIFIED,
new Header[]{
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader(HttpHeaders.ETAG, "\"some-etag\""),
new BasicHeader("X-custom", "my stuff")
},
resource
)));
Assertions.assertFalse(newEntry.isVariantRoot());
Assertions.assertFalse(newEntry.hasVariants());
}
@Test
public void testCreateUpdatedResourceEntry() {
final Resource resource = HttpTestUtils.makeRandomResource(128);
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
tenSecondsAgo,
twoSecondsAgo,
Method.GET,
"/stuff",
new Header[]{
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
},
HttpStatus.SC_NOT_MODIFIED,
new Header[]{
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader(HttpHeaders.ETAG, "\"some-etag\""),
new BasicHeader("X-custom", "my stuff"),
new BasicHeader("Cache-Control", "max-age=0")
},
resource
);
response.setHeaders(
new BasicHeader(HttpHeaders.ETAG, "\"some-new-etag\""),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneSecondAgo)),
new BasicHeader("Cache-Control", "public")
);
final HttpCacheEntry updatedEntry = impl.createUpdated(tenSecondsAgo, oneSecondAgo, response, entry);
MatcherAssert.assertThat(updatedEntry, HttpCacheEntryMatcher.equivalent(
HttpTestUtils.makeCacheEntry(
tenSecondsAgo,
oneSecondAgo,
Method.GET,
"/stuff",
new Header[]{
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ACCEPT, "stuff"),
new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en, de")
},
HttpStatus.SC_NOT_MODIFIED,
new Header[]{
new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(twoSecondsAgo)),
new BasicHeader("X-custom", "my stuff"),
new BasicHeader(HttpHeaders.ETAG, "\"some-new-etag\""),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneSecondAgo)),
new BasicHeader("Cache-Control", "public")
},
resource
)));
Assertions.assertFalse(updatedEntry.isVariantRoot());
Assertions.assertFalse(updatedEntry.hasVariants());
}
@Test
public void testUpdateNotModifiedIfResponseOlder() {
entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now,
new BasicHeader("Date", DateUtils.formatStandardDate(oneSecondAgo)),
new BasicHeader("ETag", "\"new-etag\""));
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
response.setHeader("ETag", "\"old-etag\"");
final HttpCacheEntry newEntry = impl.createUpdated(Instant.now(), Instant.now(), response, entry);
Assertions.assertSame(newEntry, entry);
}
@Test
public void testUpdateHasLatestRequestAndResponseDates() {
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo);
final HttpCacheEntry updated = impl.createUpdated(twoSecondsAgo, oneSecondAgo, response, entry);
Assertions.assertEquals(twoSecondsAgo, updated.getRequestInstant());
Assertions.assertEquals(oneSecondAgo, updated.getResponseInstant());
}
@Test
public void cannotUpdateFromANon304OriginResponse() throws Exception {
entry = HttpTestUtils.makeCacheEntry();
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
Assertions.assertThrows(IllegalArgumentException.class, () ->
impl.createUpdated(Instant.now(), Instant.now(), response, entry));
}
}

View File

@ -36,7 +36,7 @@ import java.util.Objects;
import java.util.Random;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.CacheHeaderSupport;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest;
@ -131,7 +131,7 @@ public class HttpTestUtils {
*/
public static boolean isEndToEndHeaderSubset(final HttpMessage r1, final HttpMessage r2) {
for (final Header h : r1.getHeaders()) {
if (!CacheHeaderSupport.isHopByHop(h)) {
if (!HttpCacheEntryFactory.isHopByHop(h)) {
final String r1val = getCanonicalHeaderValue(r1, h.getName());
final String r2val = getCanonicalHeaderValue(r2, h.getName());
if (!r1val.equals(r2val)) {
@ -186,13 +186,23 @@ public class HttpTestUtils {
isEndToEndHeaderSubset(r1, r2);
}
public static byte[] getRandomBytes(final int nbytes) {
public static byte[] makeRandomBytes(final int nbytes) {
final byte[] bytes = new byte[nbytes];
new Random().nextBytes(bytes);
return bytes;
}
public static ByteArrayBuffer getRandomBuffer(final int nbytes) {
public static Resource makeRandomResource(final int nbytes) {
final byte[] bytes = new byte[nbytes];
new Random().nextBytes(bytes);
return new HeapResource(bytes);
}
public static Resource makeNullResource() {
return null;
}
public static ByteArrayBuffer makeRandomBuffer(final int nbytes) {
final ByteArrayBuffer buf = new ByteArrayBuffer(nbytes);
buf.setLength(nbytes);
new Random().nextBytes(buf.array());
@ -204,7 +214,7 @@ public class HttpTestUtils {
* @return an {@link HttpEntity}
*/
public static HttpEntity makeBody(final int nbytes) {
return new ByteArrayEntity(getRandomBytes(nbytes), null);
return new ByteArrayEntity(makeRandomBytes(nbytes), null);
}
public static HeaderGroup headers(final Header... headers) {
@ -222,37 +232,57 @@ public class HttpTestUtils {
final Header[] requestHeaders,
final int status,
final Header[] responseHeaders,
final Resource resource,
final Map<String, String> variantMap) {
return new HttpCacheEntry(requestDate, responseDate, method.name(), requestUri,
headers(requestHeaders), status, headers(responseHeaders), resource, variantMap);
return new HttpCacheEntry(
requestDate,
responseDate,
method.name(), requestUri, headers(requestHeaders),
status, headers(responseHeaders),
null,
variantMap);
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate,
final Method method,
final String requestUri,
final Header[] requestHeaders,
final int status,
final Header[] responseHeaders,
final byte[] content,
final Map<String, String> variantMap) {
return new HttpCacheEntry(requestDate, responseDate, method.name(), "/",
headers(), status, headers(responseHeaders), content != null ? new HeapResource(content) : null, variantMap);
final Resource resource) {
return new HttpCacheEntry(
requestDate,
responseDate,
method.name(), requestUri, headers(requestHeaders),
status, headers(responseHeaders),
resource,
null);
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate,
final int status,
final Header[] responseHeaders,
final Resource resource,
final Map<String, String> variantMap) {
return new HttpCacheEntry(requestDate, responseDate, Method.GET.name(), "/",
headers(), status, headers(responseHeaders), resource, variantMap);
return makeCacheEntry(
requestDate,
responseDate,
Method.GET, "/", null,
status, responseHeaders,
variantMap);
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate, final Instant responseDate) {
final Duration diff = Duration.between(requestDate, responseDate);
final Instant when = requestDate.plusMillis(diff.toMillis() / 2);
return makeCacheEntry(requestDate, responseDate, getStockHeaders(when));
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate,
final int status,
final Header[] responseHeaders,
final Resource resource) {
return makeCacheEntry(
requestDate,
responseDate,
Method.GET, "/", null,
status, responseHeaders,
resource);
}
public static Header[] getStockHeaders(final Instant when) {
@ -262,28 +292,30 @@ public class HttpTestUtils {
};
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate, final Header... headers) {
final byte[] bytes = getRandomBytes(128);
return makeCacheEntry(requestDate, responseDate, headers, bytes);
public static HttpCacheEntry makeCacheEntry(final Instant requestDate, final Instant responseDate) {
final Duration diff = Duration.between(requestDate, responseDate);
final Instant when = requestDate.plusMillis(diff.toMillis() / 2);
return makeCacheEntry(requestDate, responseDate, getStockHeaders(when));
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate, final Header[] headers, final byte[] bytes) {
return makeCacheEntry(requestDate, responseDate, headers, bytes, null);
}
public static HttpCacheEntry makeCacheEntry(final Map<String,String> variantMap) {
final Instant now = Instant.now();
return makeCacheEntry(now, now, Method.GET, "/", null, HttpStatus.SC_OK, getStockHeaders(now),
new HeapResource(getRandomBytes(128)), variantMap);
final Instant responseDate,
final Header[] headers,
final byte[] bytes) {
return makeCacheEntry(requestDate, responseDate, HttpStatus.SC_OK, headers, new HeapResource(bytes));
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate, final Header[] headers, final byte[] bytes,
final Map<String,String> variantMap) {
return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK,
headers, new HeapResource(bytes), variantMap);
final Instant responseDate,
final Header... headers) {
return makeCacheEntry(requestDate, responseDate, headers, makeRandomBytes(128));
}
public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
final Instant responseDate,
final Header[] headers,
final Map<String,String> variantMap) {
return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variantMap);
}
public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {
@ -297,7 +329,7 @@ public class HttpTestUtils {
}
public static HttpCacheEntry makeCacheEntry(final Header... headers) {
return makeCacheEntry(headers, getRandomBytes(128));
return makeCacheEntry(headers, makeRandomBytes(128));
}
public static HttpCacheEntry makeCacheEntry() {

View File

@ -183,9 +183,11 @@ public class TestBasicHttpCache {
assertFalse(entry.hasVariants());
final HttpHost host = new HttpHost("foo.example.com");
final HttpRequest req = new HttpGet("http://foo.example.com/bar");
final HttpResponse resp = HttpTestUtils.make200Response();
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
impl.storeInCache(key, host, req, entry);
impl.storeInCache(host, req, resp, Instant.now(), Instant.now(), key, entry);
assertSame(entry, backing.map.get(key));
}
@ -219,7 +221,7 @@ public class TestBasicHttpCache {
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
origResponse.setHeader("Cache-Control", "max-age=3600, public");
@ -227,7 +229,7 @@ public class TestBasicHttpCache {
origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip");
impl.createCacheEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
final HttpCacheEntry result = impl.getCacheEntry(host, request);
@ -241,7 +243,7 @@ public class TestBasicHttpCache {
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding","gzip");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
origResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
origResponse.setHeader("Cache-Control", "max-age=3600, public");
@ -249,7 +251,7 @@ public class TestBasicHttpCache {
origResponse.setHeader("Vary", "Accept-Encoding");
origResponse.setHeader("Content-Encoding","gzip");
impl.createCacheEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
impl.createEntry(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
request.setHeader("Accept-Encoding","gzip");
@ -264,7 +266,7 @@ public class TestBasicHttpCache {
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
origRequest.setHeader("Accept-Encoding", "gzip");
final ByteArrayBuffer buf = HttpTestUtils.getRandomBuffer(128);
final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
// Create two response variants with different Date headers
final HttpResponse origResponse1 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
@ -280,8 +282,8 @@ public class TestBasicHttpCache {
origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
// Store the two variants in cache
impl.createCacheEntry(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
impl.createCacheEntry(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
impl.createEntry(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
impl.createEntry(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
request.setHeader("Accept-Encoding", "gzip");
@ -332,8 +334,8 @@ public class TestBasicHttpCache {
resp2.setHeader("Content-Encoding","gzip");
resp2.setHeader("Vary", "Accept-Encoding");
impl.createCacheEntry(host, req1, resp1, null, Instant.now(), Instant.now());
impl.createCacheEntry(host, req2, resp2, null, Instant.now(), Instant.now());
impl.createEntry(host, req1, resp1, null, Instant.now(), Instant.now());
impl.createEntry(host, req2, resp2, null, Instant.now(), Instant.now());
final Map<String,Variant> variants = impl.getVariantCacheEntriesWithEtags(host, req1);

View File

@ -36,7 +36,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
@ -275,8 +274,6 @@ public class TestByteArrayCacheEntrySerializer {
for (int i = 0; i < headers.length; i++) {
headers[i] = new BasicHeader("header" + i, "value" + i);
}
final String body = "Lorem ipsum dolor sit amet";
final Map<String,String> variantMap = new HashMap<>();
variantMap.put("test variant 1","true");
variantMap.put("test variant 2","true");
@ -285,7 +282,6 @@ public class TestByteArrayCacheEntrySerializer {
Instant.now(),
HttpStatus.SC_OK,
headers,
new HeapResource(body.getBytes(StandardCharsets.UTF_8)),
variantMap);
return new HttpCacheStorageEntry(key, cacheEntry);
@ -296,8 +292,6 @@ public class TestByteArrayCacheEntrySerializer {
for (int i = 0; i < headers.length; i++) {
headers[i] = new BasicHeader("header" + i, "value" + i);
}
final String body = "Lorem ipsum dolor sit amet";
final Map<String,String> variantMap = new HashMap<>();
variantMap.put("test variant 1","true");
variantMap.put("test variant 2","true");
@ -306,7 +300,6 @@ public class TestByteArrayCacheEntrySerializer {
Instant.now(),
HttpStatus.SC_OK,
headers,
new HeapResource(body.getBytes(StandardCharsets.UTF_8)),
variantMap);
return new HttpCacheStorageEntry(key, cacheEntry);

View File

@ -1,266 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class TestCacheUpdateHandler {
private Instant requestDate;
private Instant responseDate;
private CacheUpdateHandler impl;
private HttpCacheEntry entry;
private Instant now;
private Instant oneSecondAgo;
private Instant twoSecondsAgo;
private Instant eightSecondsAgo;
private Instant tenSecondsAgo;
private HttpResponse response;
@BeforeEach
public void setUp() throws Exception {
requestDate = Instant.now().minusSeconds(1);
responseDate = Instant.now();
now = Instant.now();
oneSecondAgo = now.minusSeconds(1);
twoSecondsAgo = now.minusSeconds(2);
eightSecondsAgo = now.minusSeconds(8);
tenSecondsAgo = now.minusSeconds(10);
response = new BasicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
impl = new CacheUpdateHandler();
}
@Test
public void testUpdateCacheEntryReturnsDifferentEntryInstance()
throws IOException {
entry = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry newEntry = impl.updateCacheEntry(null, entry,
requestDate, responseDate, response);
assertNotSame(newEntry, entry);
}
@Test
public void testHeadersAreMergedCorrectly() throws IOException {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("ETag", "\"etag\"")};
entry = HttpTestUtils.makeCacheEntry(headers);
response.setHeaders();
final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
Instant.now(), Instant.now(), response);
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(responseDate)));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
}
@Test
public void testNewerHeadersReplaceExistingHeaders() throws IOException {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("Cache-Control", "private"),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("Cache-Control", "max-age=0"),};
entry = HttpTestUtils.makeCacheEntry(headers);
response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"));
final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
Instant.now(), Instant.now(), response);
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(requestDate)));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatStandardDate(responseDate)));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
}
@Test
public void testNewHeadersAreAddedByMerge() throws IOException {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("ETag", "\"etag\"")};
entry = HttpTestUtils.makeCacheEntry(headers);
response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"));
final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
Instant.now(), Instant.now(), response);
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(requestDate)));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("ETag", "\"etag\""));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Last-Modified", DateUtils.formatStandardDate(responseDate)));
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Cache-Control", "public"));
}
@Test
public void oldHeadersRetainedIfResponseOlderThanEntry()
throws Exception {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(oneSecondAgo)),
new BasicHeader("ETag", "\"new-etag\"")
};
entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now, headers);
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
response.setHeader("ETag", "\"old-etag\"");
final HttpCacheEntry result = impl.updateCacheEntry("A", entry, Instant.now(),
Instant.now(), response);
assertThat(result, ContainsHeaderMatcher.contains("Date", DateUtils.formatStandardDate(oneSecondAgo)));
assertThat(result, ContainsHeaderMatcher.contains("ETag", "\"new-etag\""));
}
@Test
public void testUpdatedEntryHasLatestRequestAndResponseDates()
throws IOException {
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo);
final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
twoSecondsAgo, oneSecondAgo, response);
assertEquals(twoSecondsAgo, updated.getRequestInstant());
assertEquals(oneSecondAgo, updated.getResponseInstant());
}
@Test
public void entry1xxWarningsAreRemovedOnUpdate() throws Exception {
final Header[] headers = {
new BasicHeader("Warning", "110 fred \"Response is stale\""),
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", DateUtils.formatStandardDate(eightSecondsAgo))
};
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
twoSecondsAgo, oneSecondAgo, response);
assertEquals(0, updated.getHeaders("Warning").length);
}
@Test
public void entryWithMalformedDateIsStillUpdated() throws Exception {
final Header[] headers = {
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", "bad-date")
};
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
final HttpCacheEntry updated = impl.updateCacheEntry(null, entry,
twoSecondsAgo, oneSecondAgo, response);
assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
}
@Test
public void entryIsStillUpdatedByResponseWithMalformedDate() throws Exception {
final Header[] headers = {
new BasicHeader("ETag", "\"old\""),
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
};
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, headers);
response.setHeader("ETag", "\"new\"");
response.setHeader("Date", "bad-date");
final HttpCacheEntry updated = impl.updateCacheEntry(null, entry, twoSecondsAgo,
oneSecondAgo, response);
assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
}
@Test
public void cannotUpdateFromANon304OriginResponse() throws Exception {
entry = HttpTestUtils.makeCacheEntry();
response = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
Assertions.assertThrows(IllegalArgumentException.class, () ->
impl.updateCacheEntry("A", entry, Instant.now(), Instant.now(), response));
}
@Test
public void testCacheUpdateAddsVariantURIToParentEntry() throws Exception {
final String parentCacheKey = "parentCacheKey";
final String variantCacheKey = "variantCacheKey";
final String existingVariantKey = "existingVariantKey";
final String newVariantCacheKey = "newVariantCacheKey";
final String newVariantKey = "newVariantKey";
final Map<String,String> existingVariants = new HashMap<>();
existingVariants.put(existingVariantKey, variantCacheKey);
final HttpCacheEntry parent = HttpTestUtils.makeCacheEntry(existingVariants);
final HttpCacheEntry variant = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry result = impl.updateParentCacheEntry(parentCacheKey, parent, variant, newVariantKey, newVariantCacheKey);
final Map<String,String> resultMap = result.getVariantMap();
assertEquals(2, resultMap.size());
assertEquals(variantCacheKey, resultMap.get(existingVariantKey));
assertEquals(newVariantCacheKey, resultMap.get(newVariantKey));
}
@Test
public void testContentEncodingHeaderIsNotUpdatedByMerge() throws IOException {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(requestDate)),
new BasicHeader("ETag", "\"etag\""),
new BasicHeader("Content-Encoding", "identity")};
entry = HttpTestUtils.makeCacheEntry(headers);
response.setHeaders(new BasicHeader("Last-Modified", DateUtils.formatStandardDate(responseDate)),
new BasicHeader("Cache-Control", "public"),
new BasicHeader("Content-Encoding", "gzip"));
final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
Instant.now(), Instant.now(), response);
assertThat(updatedEntry, ContainsHeaderMatcher.contains("Content-Encoding", "identity"));
assertThat(updatedEntry, Matchers.not(ContainsHeaderMatcher.contains("Content-Encoding", "gzip")));
}
}

View File

@ -33,7 +33,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.time.Instant;
import java.util.HashMap;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
@ -54,7 +53,7 @@ public class TestCachedHttpResponseGenerator {
@BeforeEach
public void setUp() {
entry = HttpTestUtils.makeCacheEntry(new HashMap<>());
entry = HttpTestUtils.makeCacheEntry();
request = HttpTestUtils.makeDefaultRequest();
mockValidityPolicy = mock(CacheValidityPolicy.class);
impl = new CachedHttpResponseGenerator(mockValidityPolicy);

View File

@ -258,7 +258,10 @@ public class TestCachedResponseSuitabilityChecker {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
};
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, Method.HEAD, HttpStatus.SC_OK, headers, null, null);
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo,
Method.HEAD, "/", null,
HttpStatus.SC_OK, headers,
HttpTestUtils.makeNullResource());
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
@ -272,7 +275,10 @@ public class TestCachedResponseSuitabilityChecker {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
};
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, Method.GET, HttpStatus.SC_OK, headers, HttpTestUtils.getRandomBytes(128), null);
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo,
Method.GET, "/", null,
HttpStatus.SC_OK, headers,
HttpTestUtils.makeRandomResource(128));
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
@ -286,7 +292,10 @@ public class TestCachedResponseSuitabilityChecker {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
};
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, Method.GET, HttpStatus.SC_OK, headers, null, null);
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo,
Method.GET, "/", null,
HttpStatus.SC_OK, headers,
HttpTestUtils.makeNullResource());
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
@ -301,7 +310,10 @@ public class TestCachedResponseSuitabilityChecker {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
};
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, Method.GET, HttpStatus.SC_OK, headers, null, null);
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo,
Method.GET, "/", null,
HttpStatus.SC_OK, headers,
HttpTestUtils.makeNullResource());
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();
@ -315,7 +327,10 @@ public class TestCachedResponseSuitabilityChecker {
final Header[] headers = {
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
};
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo, Method.HEAD, HttpStatus.SC_OK, headers, null, null);
entry = HttpTestUtils.makeCacheEntry(elevenSecondsAgo, nineSecondsAgo,
Method.HEAD, "/", null,
HttpStatus.SC_OK, headers,
HttpTestUtils.makeNullResource());
responseCacheControl = ResponseCacheControl.builder()
.setMaxAge(3600)
.build();

View File

@ -39,9 +39,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@ -52,6 +50,7 @@ import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpCacheContext;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.classic.ExecChain;
@ -85,7 +84,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class TestCachingExecChain {
@ -95,53 +93,52 @@ public class TestCachingExecChain {
ExecRuntime mockExecRuntime;
@Mock
HttpCacheStorage mockStorage;
private DefaultCacheRevalidator cacheRevalidator;
private CachedHttpResponseGenerator responseGenerator;
@Spy
HttpCache cache = new BasicHttpCache();
@Mock
CacheUpdateHandler cacheUpdateHandler;
DefaultCacheRevalidator cacheRevalidator;
@Mock
CachedHttpResponseGenerator responseGenerator;
@Mock
HttpCacheEntryFactory cacheEntryFactory;
@Mock
CacheValidityPolicy validityPolicy;
@Mock
ResponseCachingPolicy responseCachingPolicy;
@Mock
CacheableRequestPolicy cacheableRequestPolicy;
@Mock
CachedResponseSuitabilityChecker suitabilityChecker;
@Mock
ResponseProtocolCompliance responseCompliance;
@Mock
RequestProtocolCompliance requestCompliance;
@Mock
ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder;
@Mock
HttpCache responseCache;
CacheConfig config;
HttpRoute route;
HttpHost host;
ClassicHttpRequest request;
HttpCacheContext context;
HttpCacheEntry entry;
HttpCache cache;
CachingExec impl;
CacheValidityPolicy validityPolicy;
ResponseCachingPolicy responseCachingPolicy;
CacheableRequestPolicy cacheableRequestPolicy;
CachedResponseSuitabilityChecker suitabilityChecker;
ResponseProtocolCompliance responseCompliance;
RequestProtocolCompliance requestCompliance;
ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder;
CacheConfig customConfig;
HttpCache responseCache;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
config = CacheConfig.DEFAULT;
host = new HttpHost("foo.example.com", 80);
route = new HttpRoute(host);
request = new BasicClassicHttpRequest("GET", "/stuff");
context = HttpCacheContext.create();
entry = HttpTestUtils.makeCacheEntry();
cacheRevalidator = mock(DefaultCacheRevalidator.class);
responseGenerator = mock(CachedHttpResponseGenerator.class);
customConfig = mock(CacheConfig.class);
customConfig = CacheConfig.DEFAULT;
validityPolicy = mock(CacheValidityPolicy.class);
responseCachingPolicy = mock(ResponseCachingPolicy.class);
cacheableRequestPolicy = mock(CacheableRequestPolicy.class);
suitabilityChecker = mock(CachedResponseSuitabilityChecker.class);
responseCompliance = mock(ResponseProtocolCompliance.class);
requestCompliance = mock(RequestProtocolCompliance.class);
conditionalRequestBuilder = mock(ConditionalRequestBuilder.class);
responseCache = mock(HttpCache.class);
cache = Mockito.spy(new BasicHttpCache());
impl = new CachingExec(cache, null, CacheConfig.DEFAULT);
impl = new CachingExec(cache, null, CacheConfig.DEFAULT, cacheUpdateHandler);
}
public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
@ -163,7 +160,7 @@ public class TestCachingExecChain {
execute(req2);
Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
Mockito.verify(cache).createCacheEntry(Mockito.eq(host), RequestEquivalent.eq(req1),
Mockito.verify(cache).createEntry(Mockito.eq(host), RequestEquivalent.eq(req1),
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
}
@ -1054,11 +1051,6 @@ public class TestCachingExecChain {
});
}
@Test
public void testIsSharedCache() {
Assertions.assertTrue(config.isSharedCache());
}
@Test
public void testTooLargeResponsesAreNotCached() throws Exception {
final HttpHost host = new HttpHost("foo.example.com");
@ -1078,7 +1070,7 @@ public class TestCachingExecChain {
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
Mockito.verify(cache, Mockito.never()).createCacheEntry(
Mockito.verify(cache, Mockito.never()).createEntry(
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
}
@ -1104,7 +1096,7 @@ public class TestCachingExecChain {
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
Mockito.when(mockCache.createCacheEntry(
Mockito.when(mockCache.createEntry(
Mockito.eq(host),
RequestEquivalent.eq(request),
ResponseEquivalent.eq(response),
@ -1115,7 +1107,7 @@ public class TestCachingExecChain {
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockExecRuntime, context);
impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
Mockito.verify(mockCache).createCacheEntry(
Mockito.verify(mockCache).createEntry(
Mockito.any(),
Mockito.any(),
Mockito.any(),
@ -1426,13 +1418,15 @@ public class TestCachingExecChain {
@Test
public void testNotModifiedResponseUpdatesCacheEntry() throws Exception {
final HttpCache mockCache = mock(HttpCache.class);
impl = new CachingExec(mockCache, null, CacheConfig.DEFAULT);
// Prepare request and host
final HttpHost host = new HttpHost("foo.example.com");
final ClassicHttpRequest request = new HttpGet("http://foo.example.com/bar");
// Prepare original cache entry
final HttpCacheEntry originalEntry = HttpTestUtils.makeCacheEntry();
Mockito.when(cache.getCacheEntry(host, request)).thenReturn(originalEntry);
Mockito.when(mockCache.getCacheEntry(host, request)).thenReturn(originalEntry);
// Prepare 304 Not Modified response
final Instant now = Instant.now();
@ -1451,30 +1445,26 @@ public class TestCachingExecChain {
}
final String body = "Lorem ipsum dolor sit amet";
final Map<String, String> variantMap = new HashMap<>();
variantMap.put("test variant 1", "true");
variantMap.put("test variant 2", "true");
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(
Instant.now(),
Instant.now(),
HttpStatus.SC_NOT_MODIFIED,
headers,
new HeapResource(body.getBytes(StandardCharsets.UTF_8)),
variantMap);
new HeapResource(body.getBytes(StandardCharsets.UTF_8)));
Mockito.when(cacheUpdateHandler.updateCacheEntry(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(cacheEntry);
Mockito.when(mockCache.updateEntry(Mockito.eq(host), Mockito.eq(request), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(cacheEntry);
// Call cacheAndReturnResponse with 304 Not Modified response
final ClassicHttpResponse cachedResponse = impl.cacheAndReturnResponse(host, request, backendResponse, scope, requestSent, responseReceived);
// Verify cache entry is updated
Mockito.verify(cacheUpdateHandler).updateCacheEntry(
request.getMethod(),
Mockito.verify(mockCache).updateEntry(
host,
request,
originalEntry,
backendResponse,
requestSent,
responseReceived,
backendResponse
responseReceived
);
// Verify response is generated from the updated cache entry

View File

@ -1850,7 +1850,7 @@ public class TestProtocolRequirements {
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(notModified);
Mockito.when(mockCache.updateCacheEntry(
Mockito.when(mockCache.updateEntry(
Mockito.eq(host),
RequestEquivalent.eq(request),
Mockito.eq(entry),
@ -1861,7 +1861,7 @@ public class TestProtocolRequirements {
execute(request);
Mockito.verify(mockCache).updateCacheEntry(
Mockito.verify(mockCache).updateEntry(
Mockito.any(),
Mockito.any(),
Mockito.any(),
@ -2185,7 +2185,7 @@ public class TestProtocolRequirements {
Mockito.when(mockCache.getCacheEntry(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(entry);
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(validated);
Mockito.when(mockCache.createCacheEntry(
Mockito.when(mockCache.createEntry(
Mockito.any(),
Mockito.any(),
ResponseEquivalent.eq(validated),
@ -2214,7 +2214,7 @@ public class TestProtocolRequirements {
}
Assertions.assertTrue(found113Warning);
}
Mockito.verify(mockCache).createCacheEntry(
Mockito.verify(mockCache).createEntry(
Mockito.any(),
Mockito.any(),
Mockito.any(),

View File

@ -175,7 +175,7 @@ public class TestRFC5861Compliance {
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
final byte[] body101 = HttpTestUtils.getRandomBytes(101);
final byte[] body101 = HttpTestUtils.makeRandomBytes(101);
final ByteArrayInputStream buf = new ByteArrayInputStream(body101);
final ConsumableInputStream cis = new ConsumableInputStream(buf);
final HttpEntity entity = new InputStreamEntity(cis, 101, null);