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:
parent
1d9d6d70c9
commit
6f1fd6d26b
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
287
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java
vendored
Normal file
287
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
430
httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java
vendored
Normal file
430
httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java
vendored
Normal 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue