From 6f1fd6d26beff7e64205092f30068c2c188789dd Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sun, 18 Jun 2023 17:09:22 +0200 Subject: [PATCH] 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 --- .../http/cache/CacheHeaderSupport.java | 93 ---- .../hc/client5/http/cache/HttpCacheEntry.java | 76 +--- .../http/cache/HttpCacheEntryFactory.java | 287 ++++++++++++ .../http/impl/cache/AsyncCachingExec.java | 16 +- .../http/impl/cache/BasicHttpAsyncCache.java | 244 +++++----- .../http/impl/cache/BasicHttpCache.java | 130 +++--- .../http/impl/cache/CacheUpdateHandler.java | 187 -------- .../cache/CachedHttpResponseGenerator.java | 5 - .../client5/http/impl/cache/CachingExec.java | 93 +--- .../cache/CachingH2AsyncClientBuilder.java | 2 + .../cache/CachingHttpAsyncClientBuilder.java | 2 + .../impl/cache/CachingHttpClientBuilder.java | 2 + .../http/impl/cache/HttpAsyncCache.java | 15 +- .../hc/client5/http/impl/cache/HttpCache.java | 13 +- .../http/cache/TestCacheHeaderSupport.java | 67 --- .../http/cache/TestHttpCacheEntryFactory.java | 430 ++++++++++++++++++ .../http/impl/cache/HttpTestUtils.java | 104 +++-- .../http/impl/cache/TestBasicHttpCache.java | 22 +- .../TestByteArrayCacheEntrySerializer.java | 7 - .../impl/cache/TestCacheUpdateHandler.java | 266 ----------- .../TestCachedHttpResponseGenerator.java | 3 +- .../TestCachedResponseSuitabilityChecker.java | 25 +- .../http/impl/cache/TestCachingExecChain.java | 92 ++-- .../impl/cache/TestProtocolRequirements.java | 8 +- .../impl/cache/TestRFC5861Compliance.java | 2 +- 25 files changed, 1106 insertions(+), 1085 deletions(-) delete mode 100644 httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheHeaderSupport.java create mode 100644 httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java delete mode 100644 httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java delete mode 100644 httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestCacheHeaderSupport.java create mode 100644 httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java delete mode 100644 httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheUpdateHandler.java diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheHeaderSupport.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheHeaderSupport.java deleted file mode 100644 index 0cf6ae72a..000000000 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/CacheHeaderSupport.java +++ /dev/null @@ -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 - * . - * - */ -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 HOP_BY_HOP; - - static { - final TreeSet 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 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 result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - result.addAll(HOP_BY_HOP); - result.addAll(MessageSupport.parseTokens(connectionHeader)); - return result; - } else { - return HOP_BY_HOP; - } - } - -} diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java index f3a6ca9a9..575577ec8 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java @@ -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 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 variantMap) { - Args.notNull(requestDate, "Request date"); - Args.notNull(responseDate, "Response date"); - Args.notNull(request, "Request"); - Args.notNull(response, "Origin response"); - - final Set requestHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(request); - final HeaderGroup requestHeaders = new HeaderGroup(); - for (final Iterator
it = request.headerIterator(); it.hasNext(); ) { - final Header header = it.next(); - if (!requestHopByHop.contains(header.getName().toLowerCase(Locale.ROOT))) { - requestHeaders.addHeader(header); - } - } - - final Set responseHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(request); - final HeaderGroup responseHeaders = new HeaderGroup(); - for (final Iterator
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; } /** diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java new file mode 100644 index 000000000..3ac432b6a --- /dev/null +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntryFactory.java @@ -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 + * . + * + */ +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 HOP_BY_HOP; + + static { + final TreeSet 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
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
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 responseHopByHop = HttpCacheEntryFactory.hopByHopConnectionSpecific(response); + for (final Iterator
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 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 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 hopByHop = hopByHopConnectionSpecific(message); + final HeaderGroup headerGroup = new HeaderGroup(); + for (final Iterator
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 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); + } + +} diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java index 1ad4d14a8..4839b9f05 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java @@ -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() { @@ -993,7 +994,10 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler operation.setDependency(responseCache.reuseVariantEntryFor( target, request, - matchingVariant, + backendResponse, + responseEntry, + requestDate, + responseDate, new FutureCallback() { @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, diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java index 27a9a0e65..a963622a0 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java @@ -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 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 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() { @Override public void completed(final Boolean result) { storage.updateEntry(cacheKey, - existing -> cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey), + existing -> { + final Map variantMap = existing != null ? new HashMap<>(existing.getVariantMap()) : new HashMap<>(); + variantMap.put(variantKey, variantCacheKey); + return cacheEntryFactory.createRoot(requestSent, responseReceived, request, originResponse, variantMap); + }, new FutureCallback() { @Override @@ -263,48 +277,22 @@ class BasicHttpAsyncCache implements HttpAsyncCache { @Override public Cancellable reuseVariantEntryFor( - final HttpHost host, final HttpRequest request, final Variant variant, final FutureCallback callback) { + final HttpHost host, + final HttpRequest request, + final HttpResponse originResponse, + final HttpCacheEntry entry, + final Instant requestSent, + final Instant responseReceived, + final FutureCallback 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() { - - @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() { + final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( + requestSent, + responseReceived, + originResponse, + stale); + return storeInCache( + host, + request, + originResponse, + requestSent, + responseReceived, + cacheKey, + updatedEntry, + new FutureCallback() { - @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 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() { + final String cacheKey = cacheKeyGenerator.generateKey(host, request); + final HttpCacheEntry updatedEntry = cacheEntryFactory.createUpdated( + requestSent, + responseReceived, + originResponse, + entry); + return storeEntry(cacheKey, updatedEntry, new FutureCallback() { - @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() { + 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() { - @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 variantCacheKeys = rootEntry.getVariantMap().keySet(); complexCancellable.setDependency(storage.getEntries( variantCacheKeys, diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java index 0ec08388a..16765fa6b 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java @@ -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 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 variant : root.getVariantMap().entrySet()) { final String variantCacheKey = variant.getValue(); try { diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java deleted file mode 100644 index 0f0677322..000000000 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheUpdateHandler.java +++ /dev/null @@ -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 - * . - * - */ -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 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
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
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 responseHopByHop = CacheHeaderSupport.hopByHopConnectionSpecific(response); - for (final Iterator
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; - } - -} diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java index a17cc49b2..d00f83148 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java @@ -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; } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java index 53472b8d7..d21c969d0 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java @@ -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 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 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 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(); diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingH2AsyncClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingH2AsyncClientBuilder.java index f468cfeeb..422230e7d 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingH2AsyncClientBuilder.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingH2AsyncClientBuilder.java @@ -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()); diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java index 15dbe7c4d..5af29ba46 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java @@ -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()); diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java index f2b1e51d9..de50a7622 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java @@ -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()); diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java index 2ad576709..f4d0bba59 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java @@ -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 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 callback); } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java index a26005fef..4747ca4d1 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java @@ -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); } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestCacheHeaderSupport.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestCacheHeaderSupport.java deleted file mode 100644 index dbfaf33b4..000000000 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestCacheHeaderSupport.java +++ /dev/null @@ -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 - * . - * - */ -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 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")); - } - -} diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java new file mode 100644 index 000000000..c60ce45bd --- /dev/null +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/cache/TestHttpCacheEntryFactory.java @@ -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 + * . + * + */ +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 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 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)); + } + +} diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java index 1d626900d..2f1ca8494 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpTestUtils.java @@ -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 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 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 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 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 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 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() { diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java index a9e3b3e05..c16240d6c 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java @@ -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 variants = impl.getVariantCacheEntriesWithEtags(host, req1); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestByteArrayCacheEntrySerializer.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestByteArrayCacheEntrySerializer.java index fbf8fcb09..e03d9b395 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestByteArrayCacheEntrySerializer.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestByteArrayCacheEntrySerializer.java @@ -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 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 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); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheUpdateHandler.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheUpdateHandler.java deleted file mode 100644 index 67b17e87d..000000000 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheUpdateHandler.java +++ /dev/null @@ -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 - * . - * - */ -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 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 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"))); - } - -} diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedHttpResponseGenerator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedHttpResponseGenerator.java index 7945c76bb..602785e4d 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedHttpResponseGenerator.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedHttpResponseGenerator.java @@ -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); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java index 939e87fa2..bd23f14b5 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachedResponseSuitabilityChecker.java @@ -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(); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java index d5ca40a15..cbfbca25e 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java @@ -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 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 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 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 diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java index 7c5e4a785..f5f43cb20 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolRequirements.java @@ -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(), diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java index c4bafa07b..3c5934ea6 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestRFC5861Compliance.java @@ -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);