HTTPCLIENT-2277: Revision and simplification of the cache eviction logic; conformance to RFC 9111 section 4.4
This commit is contained in:
parent
a1d9d19b5b
commit
9bde706ae7
|
@ -41,7 +41,10 @@ import org.apache.hc.core5.http.HttpResponse;
|
|||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
@Internal
|
||||
public interface HttpAsyncCacheInvalidator {
|
||||
|
||||
|
|
|
@ -41,7 +41,10 @@ import org.apache.hc.core5.http.HttpResponse;
|
|||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 4.3
|
||||
*
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
@Internal
|
||||
public interface HttpCacheInvalidator {
|
||||
|
|
|
@ -65,7 +65,6 @@ import org.apache.hc.core5.http.HttpHost;
|
|||
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.impl.BasicEntityDetails;
|
||||
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
|
||||
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
|
||||
|
@ -237,27 +236,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
|
||||
|
||||
if (!cacheableRequestPolicy.isServableFromCache(requestCacheControl, request)) {
|
||||
LOG.debug("Request is not servable from cache");
|
||||
operation.setDependency(responseCache.flushCacheEntriesInvalidatedByRequest(target, request, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception cause) {
|
||||
asyncExecCallback.failed(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
asyncExecCallback.failed(new InterruptedIOException());
|
||||
}
|
||||
|
||||
}));
|
||||
} else {
|
||||
if (cacheableRequestPolicy.isServableFromCache(requestCacheControl, request)) {
|
||||
operation.setDependency(responseCache.match(target, request, new FutureCallback<CacheMatch>() {
|
||||
|
||||
@Override
|
||||
|
@ -291,6 +270,9 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
|
||||
}));
|
||||
|
||||
} else {
|
||||
LOG.debug("Request is not servable from cache");
|
||||
callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,7 +461,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
final HttpResponse backendResponse,
|
||||
final EntityDetails entityDetails) throws HttpException, IOException {
|
||||
responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
|
||||
responseCache.flushCacheEntriesInvalidatedByExchange(target, request, backendResponse, new FutureCallback<Boolean>() {
|
||||
responseCache.evictInvalidatedEntries(target, request, backendResponse, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
|
@ -502,24 +484,6 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
storeRequestIfModifiedSinceFor304Response(request, backendResponse);
|
||||
} else {
|
||||
LOG.debug("Backend response is not cacheable");
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
LOG.warn("Unable to flush invalidated entries from cache", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.get();
|
||||
if (cachingDataConsumer != null) {
|
||||
|
|
|
@ -26,16 +26,17 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
|
@ -47,9 +48,12 @@ import org.apache.hc.client5.http.impl.Operations;
|
|||
import org.apache.hc.core5.concurrent.Cancellable;
|
||||
import org.apache.hc.core5.concurrent.ComplexCancellable;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.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.RequestLine;
|
||||
import org.apache.hc.core5.http.message.StatusLine;
|
||||
|
@ -64,27 +68,24 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
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) {
|
||||
final CacheKeyGenerator cacheKeyGenerator) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
this.cacheEntryFactory = cacheEntryFactory;
|
||||
this.cacheKeyGenerator = cacheKeyGenerator;
|
||||
this.storage = storage;
|
||||
this.cacheInvalidator = cacheInvalidator;
|
||||
}
|
||||
|
||||
public BasicHttpAsyncCache(
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpAsyncCacheStorage storage,
|
||||
final CacheKeyGenerator cacheKeyGenerator) {
|
||||
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator, DefaultAsyncCacheInvalidator.INSTANCE);
|
||||
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator);
|
||||
}
|
||||
|
||||
public BasicHttpAsyncCache(final ResourceFactory resourceFactory, final HttpAsyncCacheStorage storage) {
|
||||
|
@ -456,57 +457,124 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
|||
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesFor(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries: {}", rootKey);
|
||||
}
|
||||
return storage.removeEntry(rootKey, new FutureCallback<Boolean>() {
|
||||
private void evictEntry(final String cacheKey) {
|
||||
storage.removeEntry(cacheKey, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
callback.completed(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
if (LOG.isWarnEnabled()) {
|
||||
if (ex instanceof ResourceIOException) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", cacheKey);
|
||||
} else {
|
||||
LOG.warn("Unexpected error removing cache entry with key {}", cacheKey, ex);
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
} else {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void evictAll(final HttpCacheEntry root, final String rootKey) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Evicting root cache entry {}", rootKey);
|
||||
}
|
||||
evictEntry(rootKey);
|
||||
if (root.isVariantRoot()) {
|
||||
for (final String variantKey : root.getVariantMap().values()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Evicting variant cache entry {}", variantKey);
|
||||
}
|
||||
evictEntry(variantKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Cancellable evict(final String rootKey) {
|
||||
return storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry root) {
|
||||
if (root != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Evicting root cache entry {}", rootKey);
|
||||
}
|
||||
evictAll(root, rootKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Cancellable evict(final String rootKey, final HttpResponse response) {
|
||||
return storage.getEntry(rootKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry root) {
|
||||
if (root != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Evicting root cache entry {}", rootKey);
|
||||
}
|
||||
final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
|
||||
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (existingETag != null && newETag != null &&
|
||||
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
|
||||
!DateSupport.isBefore(response, root, HttpHeaders.DATE)) {
|
||||
evictAll(root, rootKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByRequest(
|
||||
final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
|
||||
}
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
public Cancellable evictInvalidatedEntries(
|
||||
final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
|
||||
}
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
return cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage, callback);
|
||||
final int status = response.getCode();
|
||||
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_CLIENT_ERROR &&
|
||||
!Method.isSafe(request.getMethod())) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
evict(rootKey);
|
||||
final URI requestUri = CacheSupport.normalize(CacheSupport.getRequestUri(request, host));
|
||||
if (requestUri != null) {
|
||||
final URI contentLocation = CacheSupport.getLocationURI(requestUri, response, HttpHeaders.CONTENT_LOCATION);
|
||||
if (contentLocation != null && CacheSupport.isSameOrigin(requestUri, contentLocation)) {
|
||||
final String cacheKey = cacheKeyGenerator.generateKey(contentLocation);
|
||||
evict(cacheKey, response);
|
||||
}
|
||||
final URI location = CacheSupport.getLocationURI(requestUri, response, HttpHeaders.LOCATION);
|
||||
if (location != null && CacheSupport.isSameOrigin(requestUri, location)) {
|
||||
final String cacheKey = cacheKeyGenerator.generateKey(location);
|
||||
evict(cacheKey, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
return Operations.nonCancellable();
|
||||
|
|
|
@ -26,25 +26,29 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
|
||||
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;
|
||||
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.HttpHost;
|
||||
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.RequestLine;
|
||||
import org.apache.hc.core5.http.message.StatusLine;
|
||||
|
@ -59,27 +63,24 @@ class BasicHttpCache implements HttpCache {
|
|||
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) {
|
||||
final CacheKeyGenerator cacheKeyGenerator) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
this.cacheEntryFactory = cacheEntryFactory;
|
||||
this.cacheKeyGenerator = cacheKeyGenerator;
|
||||
this.storage = storage;
|
||||
this.cacheInvalidator = cacheInvalidator;
|
||||
}
|
||||
|
||||
public BasicHttpCache(
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpCacheStorage storage,
|
||||
final CacheKeyGenerator cacheKeyGenerator) {
|
||||
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator, new DefaultCacheInvalidator());
|
||||
this(resourceFactory, HttpCacheEntryFactory.INSTANCE, storage, cacheKeyGenerator);
|
||||
}
|
||||
|
||||
public BasicHttpCache(final ResourceFactory resourceFactory, final HttpCacheStorage storage) {
|
||||
|
@ -129,6 +130,16 @@ class BasicHttpCache implements HttpCache {
|
|||
}
|
||||
}
|
||||
|
||||
private void removeInternal(final String cacheKey) {
|
||||
try {
|
||||
storage.removeEntry(cacheKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMatch match(final HttpHost host, final HttpRequest request) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
|
@ -313,36 +324,66 @@ class BasicHttpCache implements HttpCache {
|
|||
return store(request, originResponse, requestSent, responseReceived, rootKey, hit.entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
private void evictAll(final HttpCacheEntry root, final String rootKey) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries: {}", rootKey);
|
||||
LOG.debug("Evicting root cache entry {}", rootKey);
|
||||
}
|
||||
try {
|
||||
storage.removeEntry(rootKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("I/O error removing cache entry with key {}", rootKey);
|
||||
removeInternal(rootKey);
|
||||
if (root.isVariantRoot()) {
|
||||
for (final String variantKey : root.getVariantMap().values()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Evicting variant cache entry {}", variantKey);
|
||||
}
|
||||
removeInternal(variantKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesInvalidatedByRequest(final HttpHost host, final HttpRequest request) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by request: {}; {}", host, new RequestLine(request));
|
||||
private void evict(final String rootKey) {
|
||||
final HttpCacheEntry root = getInternal(rootKey);
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
evictAll(root, rootKey);
|
||||
}
|
||||
|
||||
private void evict(final String rootKey, final HttpResponse response) {
|
||||
final HttpCacheEntry root = getInternal(rootKey);
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
|
||||
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (existingETag != null && newETag != null &&
|
||||
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
|
||||
!DateSupport.isBefore(response, root, HttpHeaders.DATE)) {
|
||||
evictAll(root, rootKey);
|
||||
}
|
||||
cacheInvalidator.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyGenerator, storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesInvalidatedByExchange(final HttpHost host, final HttpRequest request, final HttpResponse response) {
|
||||
public void evictInvalidatedEntries(final HttpHost host, final HttpRequest request, final HttpResponse response) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Flush cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
|
||||
LOG.debug("Evict cache entries invalidated by exchange: {}; {} -> {}", host, new RequestLine(request), new StatusLine(response));
|
||||
}
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
cacheInvalidator.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyGenerator, storage);
|
||||
final int status = response.getCode();
|
||||
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_CLIENT_ERROR &&
|
||||
!Method.isSafe(request.getMethod())) {
|
||||
final String rootKey = cacheKeyGenerator.generateKey(host, request);
|
||||
evict(rootKey);
|
||||
final URI requestUri = CacheSupport.normalize(CacheSupport.getRequestUri(request, host));
|
||||
if (requestUri != null) {
|
||||
final URI contentLocation = CacheSupport.getLocationURI(requestUri, response, HttpHeaders.CONTENT_LOCATION);
|
||||
if (contentLocation != null && CacheSupport.isSameOrigin(requestUri, contentLocation)) {
|
||||
final String cacheKey = cacheKeyGenerator.generateKey(contentLocation);
|
||||
evict(cacheKey, response);
|
||||
}
|
||||
final URI location = CacheSupport.getLocationURI(requestUri, response, HttpHeaders.LOCATION);
|
||||
if (location != null && CacheSupport.isSameOrigin(requestUri, location)) {
|
||||
final String cacheKey = cacheKeyGenerator.generateKey(location);
|
||||
evict(cacheKey, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.utils.URIUtils;
|
||||
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.Method;
|
||||
|
||||
class CacheInvalidatorBase {
|
||||
|
||||
static boolean shouldInvalidateHeadCacheEntry(final HttpRequest req, final HttpCacheEntry parentCacheEntry) {
|
||||
return requestIsGet(req) && isAHeadCacheEntry(parentCacheEntry);
|
||||
}
|
||||
|
||||
static boolean requestIsGet(final HttpRequest req) {
|
||||
return Method.GET.isSame(req.getMethod());
|
||||
}
|
||||
|
||||
static boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) {
|
||||
return parentCacheEntry != null && Method.HEAD.isSame(parentCacheEntry.getRequestMethod());
|
||||
}
|
||||
|
||||
static boolean isSameHost(final URI requestURI, final URI targetURI) {
|
||||
return targetURI.isAbsolute() && targetURI.getAuthority().equalsIgnoreCase(requestURI.getAuthority());
|
||||
}
|
||||
|
||||
static boolean requestShouldNotBeCached(final HttpRequest req) {
|
||||
final String method = req.getMethod();
|
||||
return notGetOrHeadRequest(method);
|
||||
}
|
||||
|
||||
static boolean notGetOrHeadRequest(final String method) {
|
||||
return !(Method.GET.isSame(method) || Method.HEAD.isSame(method));
|
||||
}
|
||||
|
||||
private static URI getLocationURI(final URI requestUri, final HttpResponse response, final String headerName) {
|
||||
final Header h = response.getFirstHeader(headerName);
|
||||
if (h == null) {
|
||||
return null;
|
||||
}
|
||||
final URI locationUri = CacheSupport.normalize(h.getValue());
|
||||
if (locationUri == null) {
|
||||
return requestUri;
|
||||
}
|
||||
if (locationUri.isAbsolute()) {
|
||||
return locationUri;
|
||||
} else {
|
||||
return URIUtils.resolve(requestUri, locationUri);
|
||||
}
|
||||
}
|
||||
|
||||
static URI getContentLocationURI(final URI requestUri, final HttpResponse response) {
|
||||
return getLocationURI(requestUri, response, HttpHeaders.CONTENT_LOCATION);
|
||||
}
|
||||
|
||||
static URI getLocationURI(final URI requestUri, final HttpResponse response) {
|
||||
return getLocationURI(requestUri, response, HttpHeaders.LOCATION);
|
||||
}
|
||||
|
||||
static boolean responseAndEntryEtagsDiffer(final HttpResponse response,
|
||||
final HttpCacheEntry entry) {
|
||||
final Header entryEtag = entry.getFirstHeader(HttpHeaders.ETAG);
|
||||
final Header responseEtag = response.getFirstHeader(HttpHeaders.ETAG);
|
||||
if (entryEtag == null || responseEtag == null) {
|
||||
return false;
|
||||
}
|
||||
return (!entryEtag.getValue().equals(responseEtag.getValue()));
|
||||
}
|
||||
|
||||
static boolean responseDateOlderThanEntryDate(final HttpResponse response, final HttpCacheEntry entry) {
|
||||
return DateSupport.isBefore(response, entry, HttpHeaders.DATE);
|
||||
}
|
||||
|
||||
}
|
|
@ -29,6 +29,7 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.BitSet;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.hc.client5.http.utils.URIUtils;
|
||||
|
@ -37,6 +38,7 @@ import org.apache.hc.core5.http.FormattedHeader;
|
|||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.MessageHeaders;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.http.message.ParserCursor;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
|
@ -170,4 +172,24 @@ public final class CacheSupport {
|
|||
}
|
||||
}
|
||||
|
||||
public static URI getLocationURI(final URI requestUri, final MessageHeaders response, final String headerName) {
|
||||
final Header h = response.getFirstHeader(headerName);
|
||||
if (h == null) {
|
||||
return null;
|
||||
}
|
||||
final URI locationUri = CacheSupport.normalize(h.getValue());
|
||||
if (locationUri == null) {
|
||||
return requestUri;
|
||||
}
|
||||
if (locationUri.isAbsolute()) {
|
||||
return locationUri;
|
||||
} else {
|
||||
return URIUtils.resolve(requestUri, locationUri);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSameOrigin(final URI requestURI, final URI targetURI) {
|
||||
return targetURI.isAbsolute() && Objects.equals(requestURI.getAuthority(), targetURI.getAuthority());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ import org.apache.hc.core5.http.HttpHost;
|
|||
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.Method;
|
||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
|
||||
|
@ -182,7 +181,6 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
final RequestCacheControl requestCacheControl = CacheControlHeaderParser.INSTANCE.parse(request);
|
||||
if (!cacheableRequestPolicy.isServableFromCache(requestCacheControl, request)) {
|
||||
LOG.debug("Request is not servable from cache");
|
||||
responseCache.flushCacheEntriesInvalidatedByRequest(target, request);
|
||||
return callBackend(target, request, scope, chain);
|
||||
}
|
||||
|
||||
|
@ -386,7 +384,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
|
||||
responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
|
||||
|
||||
responseCache.flushCacheEntriesInvalidatedByExchange(target, request, backendResponse);
|
||||
responseCache.evictInvalidatedEntries(target, request, backendResponse);
|
||||
final ResponseCacheControl responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(backendResponse);
|
||||
final boolean cacheable = responseCachingPolicy.isResponseCacheable(responseCacheControl, request, backendResponse);
|
||||
if (cacheable) {
|
||||
|
@ -394,9 +392,6 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
return cacheAndReturnResponse(target, request, backendResponse, scope, requestDate, responseDate);
|
||||
}
|
||||
LOG.debug("Backend response is not cacheable");
|
||||
if (!Method.isSafe(request.getMethod())) {
|
||||
responseCache.flushCacheEntriesFor(target, request);
|
||||
}
|
||||
return backendResponse;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,6 @@ public class CachingH2AsyncClientBuilder extends H2AsyncClientBuilder {
|
|||
private File cacheDir;
|
||||
private SchedulingStrategy schedulingStrategy;
|
||||
private CacheConfig cacheConfig;
|
||||
private HttpAsyncCacheInvalidator httpCacheInvalidator;
|
||||
private boolean deleteCache;
|
||||
|
||||
public static CachingH2AsyncClientBuilder create() {
|
||||
|
@ -100,8 +99,11 @@ public class CachingH2AsyncClientBuilder extends H2AsyncClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
public final CachingH2AsyncClientBuilder setHttpCacheInvalidator(final HttpAsyncCacheInvalidator cacheInvalidator) {
|
||||
this.httpCacheInvalidator = cacheInvalidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -140,8 +142,7 @@ public class CachingH2AsyncClientBuilder extends H2AsyncClientBuilder {
|
|||
resourceFactoryCopy,
|
||||
HttpCacheEntryFactory.INSTANCE,
|
||||
storageCopy,
|
||||
CacheKeyGenerator.INSTANCE,
|
||||
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());
|
||||
CacheKeyGenerator.INSTANCE);
|
||||
|
||||
DefaultAsyncCacheRevalidator cacheRevalidator = null;
|
||||
if (config.getAsynchronousWorkers() > 0) {
|
||||
|
|
|
@ -58,7 +58,6 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
|
|||
private File cacheDir;
|
||||
private SchedulingStrategy schedulingStrategy;
|
||||
private CacheConfig cacheConfig;
|
||||
private HttpAsyncCacheInvalidator httpCacheInvalidator;
|
||||
private boolean deleteCache;
|
||||
|
||||
public static CachingHttpAsyncClientBuilder create() {
|
||||
|
@ -100,8 +99,11 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
public final CachingHttpAsyncClientBuilder setHttpCacheInvalidator(final HttpAsyncCacheInvalidator cacheInvalidator) {
|
||||
this.httpCacheInvalidator = cacheInvalidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -140,8 +142,7 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
|
|||
resourceFactoryCopy,
|
||||
HttpCacheEntryFactory.INSTANCE,
|
||||
storageCopy,
|
||||
CacheKeyGenerator.INSTANCE,
|
||||
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());
|
||||
CacheKeyGenerator.INSTANCE);
|
||||
|
||||
DefaultAsyncCacheRevalidator cacheRevalidator = null;
|
||||
if (config.getAsynchronousWorkers() > 0) {
|
||||
|
|
|
@ -54,7 +54,6 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
|
|||
private File cacheDir;
|
||||
private SchedulingStrategy schedulingStrategy;
|
||||
private CacheConfig cacheConfig;
|
||||
private HttpCacheInvalidator httpCacheInvalidator;
|
||||
private boolean deleteCache;
|
||||
|
||||
public static CachingHttpClientBuilder create() {
|
||||
|
@ -92,8 +91,11 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
public final CachingHttpClientBuilder setHttpCacheInvalidator(final HttpCacheInvalidator cacheInvalidator) {
|
||||
this.httpCacheInvalidator = cacheInvalidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -132,8 +134,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
|
|||
resourceFactoryCopy,
|
||||
HttpCacheEntryFactory.INSTANCE,
|
||||
storageCopy,
|
||||
CacheKeyGenerator.INSTANCE,
|
||||
this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultCacheInvalidator());
|
||||
CacheKeyGenerator.INSTANCE);
|
||||
|
||||
DefaultCacheRevalidator cacheRevalidator = null;
|
||||
if (config.getAsynchronousWorkers() > 0) {
|
||||
|
|
|
@ -1,266 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.impl.Operations;
|
||||
import org.apache.hc.client5.http.utils.URIUtils;
|
||||
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.concurrent.Cancellable;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.function.Resolver;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Given a particular HTTP request / response pair, flush any cache entries
|
||||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
@Internal
|
||||
public class DefaultAsyncCacheInvalidator extends CacheInvalidatorBase implements HttpAsyncCacheInvalidator {
|
||||
|
||||
public static final DefaultAsyncCacheInvalidator INSTANCE = new DefaultAsyncCacheInvalidator();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncCacheInvalidator.class);
|
||||
|
||||
private void removeEntry(final HttpAsyncCacheStorage storage, final String cacheKey) {
|
||||
storage.removeEntry(cacheKey, new FutureCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Boolean result) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
if (result.booleanValue()) {
|
||||
LOG.debug("Cache entry with key {} successfully flushed", cacheKey);
|
||||
} else {
|
||||
LOG.debug("Cache entry with key {} could not be flushed", cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Unable to flush cache entry with key {}", cacheKey, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByRequest(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpAsyncCacheStorage storage,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
final String s = CacheSupport.getRequestUri(request, host);
|
||||
final URI uri = CacheSupport.normalize(s);
|
||||
final String cacheKey = uri != null ? cacheKeyResolver.resolve(uri) : s;
|
||||
return storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry parentEntry) {
|
||||
if (requestShouldNotBeCached(request) || shouldInvalidateHeadCacheEntry(request, parentEntry)) {
|
||||
if (parentEntry != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invalidating parentEntry cache entry with key {}", cacheKey);
|
||||
}
|
||||
for (final String variantURI : parentEntry.getVariantMap().values()) {
|
||||
removeEntry(storage, variantURI);
|
||||
}
|
||||
removeEntry(storage, cacheKey);
|
||||
}
|
||||
if (uri != null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("{} is not a valid URI", s);
|
||||
}
|
||||
final Header clHdr = request.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
|
||||
if (clHdr != null) {
|
||||
final URI contentLocation = CacheSupport.normalize(clHdr.getValue());
|
||||
if (contentLocation != null) {
|
||||
if (!flushAbsoluteUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage)) {
|
||||
flushRelativeUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
final Header lHdr = request.getFirstHeader(HttpHeaders.LOCATION);
|
||||
if (lHdr != null) {
|
||||
final URI location = CacheSupport.normalize(lHdr.getValue());
|
||||
if (location != null) {
|
||||
flushAbsoluteUriFromSameHost(uri, location, cacheKeyResolver, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void flushRelativeUriFromSameHost(
|
||||
final URI requestUri,
|
||||
final URI uri,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpAsyncCacheStorage storage) {
|
||||
final URI resolvedUri = uri != null ? URIUtils.resolve(requestUri, uri) : null;
|
||||
if (resolvedUri != null && isSameHost(requestUri, resolvedUri)) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(resolvedUri));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean flushAbsoluteUriFromSameHost(
|
||||
final URI requestUri,
|
||||
final URI uri,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpAsyncCacheStorage storage) {
|
||||
if (uri != null && isSameHost(requestUri, uri)) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(uri));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse response,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpAsyncCacheStorage storage,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
final int status = response.getCode();
|
||||
if (status >= HttpStatus.SC_SUCCESS && status < HttpStatus.SC_REDIRECTION) {
|
||||
final String s = CacheSupport.getRequestUri(request, host);
|
||||
final URI requestUri = CacheSupport.normalize(s);
|
||||
if (requestUri != null) {
|
||||
final List<String> cacheKeys = new ArrayList<>(2);
|
||||
final URI contentLocation = getContentLocationURI(requestUri, response);
|
||||
if (contentLocation != null && isSameHost(requestUri, contentLocation)) {
|
||||
cacheKeys.add(cacheKeyResolver.resolve(contentLocation));
|
||||
}
|
||||
final URI location = getLocationURI(requestUri, response);
|
||||
if (location != null && isSameHost(requestUri, location)) {
|
||||
cacheKeys.add(cacheKeyResolver.resolve(location));
|
||||
}
|
||||
if (cacheKeys.size() == 1) {
|
||||
final String key = cacheKeys.get(0);
|
||||
storage.getEntry(key, new FutureCallback<HttpCacheEntry>() {
|
||||
|
||||
@Override
|
||||
public void completed(final HttpCacheEntry entry) {
|
||||
if (entry != null) {
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
|
||||
removeEntry(storage, key);
|
||||
}
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
} else if (cacheKeys.size() > 1) {
|
||||
storage.getEntries(cacheKeys, new FutureCallback<Map<String, HttpCacheEntry>>() {
|
||||
|
||||
@Override
|
||||
public void completed(final Map<String, HttpCacheEntry> resultMap) {
|
||||
for (final Map.Entry<String, HttpCacheEntry> resultEntry: resultMap.entrySet()) {
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
final String key = resultEntry.getKey();
|
||||
final HttpCacheEntry entry = resultEntry.getValue();
|
||||
if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
|
||||
removeEntry(storage, key);
|
||||
}
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
callback.cancelled();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.completed(Boolean.TRUE);
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.utils.URIUtils;
|
||||
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.function.Resolver;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Given a particular HTTP request / response pair, flush any cache entries
|
||||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
@Internal
|
||||
public class DefaultCacheInvalidator extends CacheInvalidatorBase implements HttpCacheInvalidator {
|
||||
|
||||
public static final DefaultCacheInvalidator INSTANCE = new DefaultCacheInvalidator();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultCacheInvalidator.class);
|
||||
|
||||
private HttpCacheEntry getEntry(final HttpCacheStorage storage, final String cacheKey) {
|
||||
try {
|
||||
return storage.getEntry(cacheKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Unable to get cache entry with key {}", cacheKey, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntry(final HttpCacheStorage storage, final String cacheKey) {
|
||||
try {
|
||||
storage.removeEntry(cacheKey);
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Unable to flush cache entry with key {}", cacheKey, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesInvalidatedByRequest(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
final String s = CacheSupport.getRequestUri(request, host);
|
||||
final URI uri = CacheSupport.normalize(s);
|
||||
final String cacheKey = uri != null ? cacheKeyResolver.resolve(uri) : s;
|
||||
final HttpCacheEntry parent = getEntry(storage, cacheKey);
|
||||
|
||||
if (requestShouldNotBeCached(request) || shouldInvalidateHeadCacheEntry(request, parent)) {
|
||||
if (parent != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invalidating parent cache entry with key {}", cacheKey);
|
||||
}
|
||||
for (final String variantURI : parent.getVariantMap().values()) {
|
||||
removeEntry(storage, variantURI);
|
||||
}
|
||||
removeEntry(storage, cacheKey);
|
||||
}
|
||||
if (uri != null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("{} is not a valid URI", s);
|
||||
}
|
||||
final Header clHdr = request.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
|
||||
if (clHdr != null) {
|
||||
final URI contentLocation = CacheSupport.normalize(clHdr.getValue());
|
||||
if (contentLocation != null) {
|
||||
if (!flushAbsoluteUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage)) {
|
||||
flushRelativeUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
final Header lHdr = request.getFirstHeader(HttpHeaders.LOCATION);
|
||||
if (lHdr != null) {
|
||||
final URI location = CacheSupport.normalize(lHdr.getValue());
|
||||
if (location != null) {
|
||||
flushAbsoluteUriFromSameHost(uri, location, cacheKeyResolver, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushRelativeUriFromSameHost(
|
||||
final URI requestUri,
|
||||
final URI uri,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
final URI resolvedUri = uri != null ? URIUtils.resolve(requestUri, uri) : null;
|
||||
if (resolvedUri != null && isSameHost(requestUri, resolvedUri)) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(resolvedUri));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean flushAbsoluteUriFromSameHost(
|
||||
final URI requestUri,
|
||||
final URI uri,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
if (uri != null && isSameHost(requestUri, uri)) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(uri));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCacheEntriesInvalidatedByExchange(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final HttpResponse response,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
final int status = response.getCode();
|
||||
if (status < 200 || status > 299) {
|
||||
return;
|
||||
}
|
||||
final String s = CacheSupport.getRequestUri(request, host);
|
||||
final URI uri = CacheSupport.normalize(s);
|
||||
if (uri == null) {
|
||||
return;
|
||||
}
|
||||
final URI contentLocation = getContentLocationURI(uri, response);
|
||||
if (contentLocation != null && isSameHost(uri, contentLocation)) {
|
||||
flushLocationCacheEntry(response, contentLocation, storage, cacheKeyResolver);
|
||||
}
|
||||
final URI location = getLocationURI(uri, response);
|
||||
if (location != null && isSameHost(uri, location)) {
|
||||
flushLocationCacheEntry(response, location, storage, cacheKeyResolver);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushLocationCacheEntry(
|
||||
final HttpResponse response,
|
||||
final URI location,
|
||||
final HttpCacheStorage storage,
|
||||
final Resolver<URI, String> cacheKeyResolver) {
|
||||
final String cacheKey = cacheKeyResolver.resolve(location);
|
||||
final HttpCacheEntry entry = getEntry(storage, cacheKey);
|
||||
if (entry != null) {
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
|
||||
if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
|
||||
removeEntry(storage, cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -99,21 +99,9 @@ interface HttpAsyncCache {
|
|||
FutureCallback<CacheHit> callback);
|
||||
|
||||
/**
|
||||
* Clear all matching {@link HttpCacheEntry}s.
|
||||
* Evicts {@link HttpCacheEntry}s invalidated by the given message exchange.
|
||||
*/
|
||||
Cancellable flushCacheEntriesFor(
|
||||
HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Flush {@link HttpCacheEntry}s invalidated by the given request
|
||||
*/
|
||||
Cancellable flushCacheEntriesInvalidatedByRequest(
|
||||
HttpHost host, HttpRequest request, FutureCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
|
||||
*/
|
||||
Cancellable flushCacheEntriesInvalidatedByExchange(
|
||||
Cancellable evictInvalidatedEntries(
|
||||
HttpHost host, HttpRequest request, HttpResponse response, FutureCallback<Boolean> callback);
|
||||
|
||||
}
|
||||
|
|
|
@ -92,18 +92,8 @@ interface HttpCache {
|
|||
Instant responseReceived);
|
||||
|
||||
/**
|
||||
* Clear all matching {@link HttpCacheEntry}s.
|
||||
* Evicts {@link HttpCacheEntry}s invalidated by the given message exchange.
|
||||
*/
|
||||
void flushCacheEntriesFor(HttpHost host, HttpRequest request);
|
||||
|
||||
/**
|
||||
* Flush {@link HttpCacheEntry}s invalidated by the given request
|
||||
*/
|
||||
void flushCacheEntriesInvalidatedByRequest(HttpHost host, HttpRequest request);
|
||||
|
||||
/**
|
||||
* Flush {@link HttpCacheEntry}s invalidated by the given message exchange.
|
||||
*/
|
||||
void flushCacheEntriesInvalidatedByExchange(HttpHost host, HttpRequest request, HttpResponse response);
|
||||
void evictInvalidatedEntries(HttpHost host, HttpRequest request, HttpResponse response);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,16 +29,17 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
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.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
|
@ -389,11 +390,28 @@ public class HttpTestUtils {
|
|||
return new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
|
||||
}
|
||||
|
||||
public static Map<String, String> makeDefaultVariantMap(final String key, final String value) {
|
||||
final Map<String, String> variants = new HashMap<>();
|
||||
variants.put(key, value);
|
||||
public static <T> FutureCallback<T> countDown(final CountDownLatch latch) {
|
||||
return new FutureCallback<T>() {
|
||||
|
||||
@Override
|
||||
public void completed(final T result) {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
latch.countDown();
|
||||
Assertions.fail(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
latch.countDown();
|
||||
Assertions.fail("Unexpected cancellation");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.impl.Operations;
|
||||
import org.apache.hc.core5.concurrent.Cancellable;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
|
||||
class SimpleHttpAsyncCacheStorage implements HttpAsyncCacheStorage {
|
||||
|
||||
public final Map<String,HttpCacheEntry> map;
|
||||
|
||||
public SimpleHttpAsyncCacheStorage() {
|
||||
map = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable putEntry(final String key, final HttpCacheEntry entry, final FutureCallback<Boolean> callback) {
|
||||
map.put(key, entry);
|
||||
if (callback != null) {
|
||||
callback.completed(true);
|
||||
}
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
public void putEntry(final String key, final HttpCacheEntry entry) {
|
||||
map.put(key, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable getEntry(final String key, final FutureCallback<HttpCacheEntry> callback) {
|
||||
final HttpCacheEntry entry = map.get(key);
|
||||
if (callback != null) {
|
||||
callback.completed(entry);
|
||||
}
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
public HttpCacheEntry getEntry(final String key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable removeEntry(final String key, final FutureCallback<Boolean> callback) {
|
||||
final HttpCacheEntry removed = map.remove(key);
|
||||
if (callback != null) {
|
||||
callback.completed(removed != null);
|
||||
}
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable updateEntry(final String key, final HttpCacheCASOperation casOperation, final FutureCallback<Boolean> callback) {
|
||||
final HttpCacheEntry v1 = map.get(key);
|
||||
try {
|
||||
final HttpCacheEntry v2 = casOperation.execute(v1);
|
||||
map.put(key,v2);
|
||||
if (callback != null) {
|
||||
callback.completed(true);
|
||||
}
|
||||
} catch (final ResourceIOException ex) {
|
||||
if (callback != null) {
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable getEntries(final Collection<String> keys, final FutureCallback<Map<String, HttpCacheEntry>> callback) {
|
||||
final Map<String, HttpCacheEntry> resultMap = new HashMap<>(keys.size());
|
||||
for (final String key: keys) {
|
||||
final HttpCacheEntry entry = map.get(key);
|
||||
if (entry != null) {
|
||||
resultMap.put(key, entry);
|
||||
}
|
||||
}
|
||||
callback.completed(resultMap);
|
||||
return Operations.nonCancellable();
|
||||
}
|
||||
|
||||
}
|
534
httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java
vendored
Normal file
534
httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpAsyncCache.java
vendored
Normal file
|
@ -0,0 +1,534 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class TestBasicHttpAsyncCache {
|
||||
|
||||
private HttpHost host;
|
||||
private Instant now;
|
||||
private Instant tenSecondsAgo;
|
||||
private SimpleHttpAsyncCacheStorage mockStorage;
|
||||
private BasicHttpAsyncCache impl;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
now = Instant.now();
|
||||
tenSecondsAgo = now.minusSeconds(10);
|
||||
mockStorage = Mockito.spy(new SimpleHttpAsyncCacheStorage());
|
||||
impl = new BasicHttpAsyncCache(HeapResourceFactory.INSTANCE, mockStorage);
|
||||
}
|
||||
|
||||
// Tests
|
||||
@Test
|
||||
public void testInvalidatesUnsafeRequests() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST", "/path");
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
|
||||
mockStorage.putEntry(key, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.any());
|
||||
Assertions.assertNull(mockStorage.getEntry(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateSafeRequests() throws Exception {
|
||||
final HttpRequest request1 = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response1 = HttpTestUtils.make200Response();
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
|
||||
impl.evictInvalidatedEntries(host, request1, response1, HttpTestUtils.countDown(latch1));
|
||||
|
||||
latch1.await();
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
|
||||
final HttpRequest request2 = new BasicHttpRequest("HEAD", "/");
|
||||
final HttpResponse response2 = HttpTestUtils.make200Response();
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
|
||||
impl.evictInvalidatedEntries(host, request2, response2, HttpTestUtils.countDown(latch2));
|
||||
|
||||
latch2.await();
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST", "/path");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final String variantKey1 = "{var1}" + rootKey;
|
||||
final String variantKey2 = "{var2}" + rootKey;
|
||||
final Map<String, String> variantMap = new HashMap<>();
|
||||
variantMap.put("{var1}", variantKey1);
|
||||
variantMap.put("{var2}", variantKey2);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
|
||||
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variantMap));
|
||||
mockStorage.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
|
||||
mockStorage.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(variantKey1), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(variantKey2), Mockito.any());
|
||||
|
||||
Assertions.assertNull(mockStorage.getEntry(rootKey));
|
||||
Assertions.assertNull(mockStorage.getEntry(variantKey1));
|
||||
Assertions.assertNull(mockStorage.getEntry(variantKey2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final HttpResponse response = HttpTestUtils.make500Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
Assertions.assertNull(mockStorage.getEntry(rootKey));
|
||||
Assertions.assertNull(mockStorage.getEntry(contentKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
response.setHeader("Content-Location", "/bar");
|
||||
|
||||
mockStorage.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(rootKey), Mockito.any());
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
Assertions.assertNull(mockStorage.getEntry(rootKey));
|
||||
Assertions.assertNull(mockStorage.getEntry(contentKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationOtherOrigin() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHost("bar.example.com")
|
||||
.setPath("/")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage, Mockito.never()).getEntry(contentKey);
|
||||
verify(mockStorage, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEtagsMatch() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"same-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"same-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfOlder() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(now)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.removeHeaders("ETag");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag", "\"some-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage, Mockito.never()).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag", "\"new-etag\"");
|
||||
response.removeHeaders("Date");
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", "huh?");
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
mockStorage.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", "huh?")));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
impl.evictInvalidatedEntries(host, request, response, HttpTestUtils.countDown(latch));
|
||||
|
||||
latch.await();
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(contentKey), Mockito.any());
|
||||
}
|
||||
|
||||
}
|
|
@ -32,7 +32,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -40,9 +43,7 @@ import java.util.Map;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
|
@ -50,76 +51,33 @@ 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.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class TestBasicHttpCache {
|
||||
|
||||
private BasicHttpCache impl;
|
||||
private HttpHost host;
|
||||
private Instant now;
|
||||
private Instant tenSecondsAgo;
|
||||
private SimpleHttpCacheStorage backing;
|
||||
private BasicHttpCache impl;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
backing = new SimpleHttpCacheStorage();
|
||||
host = new HttpHost("foo.example.com");
|
||||
now = Instant.now();
|
||||
tenSecondsAgo = now.minusSeconds(10);
|
||||
backing = Mockito.spy(new SimpleHttpCacheStorage());
|
||||
impl = new BasicHttpCache(new HeapResourceFactory(), backing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushContentLocationEntryIfUnSafeRequest() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpPost("/foo");
|
||||
final HttpResponse resp = HttpTestUtils.make200Response();
|
||||
resp.setHeader("Content-Location", "/bar");
|
||||
resp.setHeader(HttpHeaders.ETAG, "\"etag\"");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, new HttpGet("/bar"));
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
|
||||
new BasicHeader("ETag", "\"old-etag\""));
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, req, resp);
|
||||
|
||||
assertNull(backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFlushContentLocationEntryIfSafeRequest() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpGet("/foo");
|
||||
final HttpResponse resp = HttpTestUtils.make200Response();
|
||||
resp.setHeader("Content-Location", "/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, new HttpGet("/bar"));
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
|
||||
new BasicHeader("ETag", "\"old-etag\""));
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, req, resp);
|
||||
|
||||
assertEquals(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanFlushCacheEntriesAtUri() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
final HttpRequest req = new HttpDelete("/bar");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
backing.map.put(key, entry);
|
||||
|
||||
impl.flushCacheEntriesFor(host, req);
|
||||
|
||||
assertNull(backing.map.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreInCachePutsNonVariantEntryInPlace() throws Exception {
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
|
@ -130,7 +88,7 @@ public class TestBasicHttpCache {
|
|||
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
|
||||
|
||||
impl.store(req, resp, Instant.now(), Instant.now(), key, entry);
|
||||
impl.store(req, resp, now, now, key, entry);
|
||||
assertSame(entry, backing.map.get(key));
|
||||
}
|
||||
|
||||
|
@ -161,20 +119,18 @@ public class TestBasicHttpCache {
|
|||
|
||||
@Test
|
||||
public void testGetCacheEntryReturnsNullIfNoVariantInCache() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
|
||||
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
|
||||
origRequest.setHeader("Accept-Encoding","gzip");
|
||||
|
||||
final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
|
||||
final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
origResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
||||
origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
origResponse.setHeader("Cache-Control", "max-age=3600, public");
|
||||
origResponse.setHeader("ETag", "\"etag\"");
|
||||
origResponse.setHeader("Vary", "Accept-Encoding");
|
||||
origResponse.setHeader("Content-Encoding","gzip");
|
||||
|
||||
impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse, buf, now, now);
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
final CacheMatch result = impl.match(host, request);
|
||||
|
@ -184,20 +140,18 @@ public class TestBasicHttpCache {
|
|||
|
||||
@Test
|
||||
public void testGetCacheEntryReturnsVariantIfPresentInCache() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
|
||||
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
|
||||
origRequest.setHeader("Accept-Encoding","gzip");
|
||||
|
||||
final ByteArrayBuffer buf = HttpTestUtils.makeRandomBuffer(128);
|
||||
final HttpResponse origResponse = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
origResponse.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
||||
origResponse.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
origResponse.setHeader("Cache-Control", "max-age=3600, public");
|
||||
origResponse.setHeader("ETag", "\"etag\"");
|
||||
origResponse.setHeader("Vary", "Accept-Encoding");
|
||||
origResponse.setHeader("Content-Encoding","gzip");
|
||||
|
||||
impl.store(host, origRequest, origResponse, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse, buf, now, now);
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
|
@ -208,8 +162,6 @@ public class TestBasicHttpCache {
|
|||
|
||||
@Test
|
||||
public void testGetCacheEntryReturnsVariantWithMostRecentDateHeader() throws Exception {
|
||||
final HttpHost host = new HttpHost("foo.example.com");
|
||||
|
||||
final HttpRequest origRequest = new HttpGet("http://foo.example.com/bar");
|
||||
origRequest.setHeader("Accept-Encoding", "gzip");
|
||||
|
||||
|
@ -217,20 +169,20 @@ public class TestBasicHttpCache {
|
|||
|
||||
// Create two response variants with different Date headers
|
||||
final HttpResponse origResponse1 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
origResponse1.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now().minusSeconds(3600)));
|
||||
origResponse1.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now.minusSeconds(3600)));
|
||||
origResponse1.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
|
||||
origResponse1.setHeader(HttpHeaders.ETAG, "\"etag1\"");
|
||||
origResponse1.setHeader(HttpHeaders.VARY, "Accept-Encoding");
|
||||
|
||||
final HttpResponse origResponse2 = new BasicHttpResponse(HttpStatus.SC_OK, "OK");
|
||||
origResponse2.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
|
||||
origResponse2.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(now));
|
||||
origResponse2.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600, public");
|
||||
origResponse2.setHeader(HttpHeaders.ETAG, "\"etag2\"");
|
||||
origResponse2.setHeader(HttpHeaders.VARY, "Accept-Encoding");
|
||||
|
||||
// Store the two variants in cache
|
||||
impl.store(host, origRequest, origResponse1, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse2, buf, Instant.now(), Instant.now());
|
||||
impl.store(host, origRequest, origResponse1, buf, now, now);
|
||||
impl.store(host, origRequest, origResponse2, buf, now, now);
|
||||
|
||||
final HttpRequest request = new HttpGet("http://foo.example.com/bar");
|
||||
request.setHeader("Accept-Encoding", "gzip");
|
||||
|
@ -276,7 +228,7 @@ public class TestBasicHttpCache {
|
|||
req1.setHeader("Accept-Encoding", "gzip");
|
||||
|
||||
final HttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
resp1.setHeader("Cache-Control", "max-age=3600, public");
|
||||
resp1.setHeader("ETag", "\"etag1\"");
|
||||
resp1.setHeader("Vary", "Accept-Encoding");
|
||||
|
@ -287,15 +239,15 @@ public class TestBasicHttpCache {
|
|||
req2.setHeader("Accept-Encoding", "identity");
|
||||
|
||||
final HttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
resp2.setHeader("Cache-Control", "max-age=3600, public");
|
||||
resp2.setHeader("ETag", "\"etag2\"");
|
||||
resp2.setHeader("Vary", "Accept-Encoding");
|
||||
resp2.setHeader("Content-Encoding","gzip");
|
||||
resp2.setHeader("Vary", "Accept-Encoding");
|
||||
|
||||
final CacheHit hit1 = impl.store(host, req1, resp1, null, Instant.now(), Instant.now());
|
||||
final CacheHit hit2 = impl.store(host, req2, resp2, null, Instant.now(), Instant.now());
|
||||
final CacheHit hit1 = impl.store(host, req1, resp1, null, now, now);
|
||||
final CacheHit hit2 = impl.store(host, req2, resp2, null, now, now);
|
||||
|
||||
final Map<String, String> variantMap = new HashMap<>();
|
||||
variantMap.put("variant-1", hit1.variantKey);
|
||||
|
@ -311,4 +263,416 @@ public class TestBasicHttpCache {
|
|||
MatcherAssert.assertThat(variants.get(hit2.getEntryKey()), HttpCacheEntryMatcher.equivalent(hit2.entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUnsafeRequests() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/path");
|
||||
final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
|
||||
backing.putEntry(key, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(key);
|
||||
verify(backing).removeEntry(key);
|
||||
|
||||
Assertions.assertNull(backing.getEntry(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateSafeRequests() throws Exception {
|
||||
final HttpRequest request1 = new BasicHttpRequest("GET","/");
|
||||
final HttpResponse response1 = HttpTestUtils.make200Response();
|
||||
|
||||
impl.evictInvalidatedEntries(host, request1, response1);
|
||||
|
||||
verifyNoMoreInteractions(backing);
|
||||
|
||||
final HttpRequest request2 = new BasicHttpRequest("HEAD","/");
|
||||
final HttpResponse response2 = HttpTestUtils.make200Response();
|
||||
impl.evictInvalidatedEntries(host, request2, response2);
|
||||
|
||||
verifyNoMoreInteractions(backing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUnsafeRequestsWithVariants() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/path");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final String variantKey1 = "{var1}" + rootKey;
|
||||
final String variantKey2 = "{var2}" + rootKey;
|
||||
final Map<String, String> variantMap = new HashMap<>();
|
||||
variantMap.put("{var1}", variantKey1);
|
||||
variantMap.put("{var2}", variantKey2);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
|
||||
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry(variantMap));
|
||||
backing.putEntry(variantKey1, HttpTestUtils.makeCacheEntry());
|
||||
backing.putEntry(variantKey2, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(rootKey);
|
||||
verify(backing).removeEntry(rootKey);
|
||||
verify(backing).removeEntry(variantKey1);
|
||||
verify(backing).removeEntry(variantKey2);
|
||||
|
||||
Assertions.assertNull(backing.getEntry(rootKey));
|
||||
Assertions.assertNull(backing.getEntry(variantKey1));
|
||||
Assertions.assertNull(backing.getEntry(variantKey2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationAndFresher() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(rootKey);
|
||||
verify(backing).removeEntry(rootKey);
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByLocationAndFresher() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(rootKey);
|
||||
verify(backing).removeEntry(rootKey);
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateForUnsuccessfulResponse() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final HttpResponse response = HttpTestUtils.make500Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verifyNoMoreInteractions(backing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationNonCanonical() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(rootKey);
|
||||
verify(backing).removeEntry(rootKey);
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
Assertions.assertNull(backing.getEntry(rootKey));
|
||||
Assertions.assertNull(backing.getEntry(contentKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidateUriSpecifiedByContentLocationRelative() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final String rootKey = CacheKeyGenerator.INSTANCE.generateKey(host, request);
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
response.setHeader("Content-Location", "/bar");
|
||||
|
||||
backing.putEntry(rootKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(rootKey);
|
||||
verify(backing).removeEntry(rootKey);
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
Assertions.assertNull(backing.getEntry(rootKey));
|
||||
Assertions.assertNull(backing.getEntry(contentKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationOtherOrigin() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHost("bar.example.com")
|
||||
.setPath("/")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry());
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing, Mockito.never()).getEntry(contentKey);
|
||||
verify(backing, Mockito.never()).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEtagsMatch() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"same-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"same-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing, Mockito.never()).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfOlder() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(now)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing, Mockito.never()).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.removeHeaders("ETag");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing, Mockito.never()).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUriSpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag", "\"some-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing, Mockito.never()).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag", "\"new-etag\"");
|
||||
response.removeHeaders("Date");
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\"")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", "huh?");
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUriSpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT", "/foo");
|
||||
final URI contentUri = new URIBuilder()
|
||||
.setHttpHost(host)
|
||||
.setPath("/bar")
|
||||
.build();
|
||||
final String contentKey = CacheKeyGenerator.INSTANCE.generateKey(contentUri);
|
||||
|
||||
final HttpResponse response = HttpTestUtils.make200Response();
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
response.setHeader("Content-Location", contentUri.toASCIIString());
|
||||
|
||||
backing.putEntry(contentKey, HttpTestUtils.makeCacheEntry(
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", "huh?")));
|
||||
|
||||
impl.evictInvalidatedEntries(host, request, response);
|
||||
|
||||
verify(backing).getEntry(contentKey);
|
||||
verify(backing).removeEntry(contentKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,685 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.concurrent.Cancellable;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.function.Resolver;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
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.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class TestDefaultAsyncCacheInvalidator {
|
||||
|
||||
private DefaultAsyncCacheInvalidator impl;
|
||||
private HttpHost host;
|
||||
@Mock
|
||||
private HttpCacheEntry mockEntry;
|
||||
@Mock
|
||||
private Resolver<URI, String> cacheKeyResolver;
|
||||
@Mock
|
||||
private HttpAsyncCacheStorage mockStorage;
|
||||
@Mock
|
||||
private FutureCallback<Boolean> operationCallback;
|
||||
@Mock
|
||||
private Cancellable cancellable;
|
||||
|
||||
private Instant now;
|
||||
private Instant tenSecondsAgo;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
now = Instant.now();
|
||||
tenSecondsAgo = now.minusSeconds(10);
|
||||
|
||||
when(cacheKeyResolver.resolve(ArgumentMatchers.any())).thenAnswer((Answer<String>) invocation -> {
|
||||
final URI uri = invocation.getArgument(0);
|
||||
return CacheSupport.normalize(uri).toASCIIString();
|
||||
});
|
||||
|
||||
host = new HttpHost("foo.example.com");
|
||||
impl = new DefaultAsyncCacheInvalidator();
|
||||
}
|
||||
|
||||
// Tests
|
||||
@Test
|
||||
public void testInvalidatesRequestsThatArentGETorHEAD() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/path");
|
||||
final String key = "http://foo.example.com:80/path";
|
||||
|
||||
final Map<String,String> variantMap = new HashMap<>();
|
||||
cacheEntryHasVariantMap(variantMap);
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUrisInContentLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://foo.example.com/content";
|
||||
request.setHeader("Content-Location", contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq("http://foo.example.com:80/content"), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUrisInLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://foo.example.com/content";
|
||||
request.setHeader("Location",contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq("http://foo.example.com:80/content"), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesRelativeUrisInContentLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String relativePath = "/content";
|
||||
request.setHeader("Content-Location",relativePath);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq("http://foo.example.com:80/content"), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUrisInContentLocationHeadersOnPUTsToDifferentHosts() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://bar.example.com/content";
|
||||
request.setHeader("Content-Location",contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq("http://foo.example.com:80/"), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD","/");
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq("http://foo.example.com:80/"), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesHEADCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
|
||||
cacheEntryisForMethod("HEAD");
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesVariantHEADCacheEntriesIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
final String theVariantKey = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}";
|
||||
final String theVariantURI = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}http://foo.example.com:80/";
|
||||
final Map<String, String> variants = HttpTestUtils.makeDefaultVariantMap(theVariantKey, theVariantURI);
|
||||
|
||||
cacheEntryisForMethod("HEAD");
|
||||
cacheEntryHasVariantMap(variants);
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(theVariantURI), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADCacheEntry() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD", uri);
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADCacheEntryIfSubsequentHEADRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD", uri);
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
|
||||
cacheEntryisForMethod("GET");
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientCacheControlHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Cache-Control","no-cache");
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq("http://foo.example.com:80/"), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientPragmaHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Pragma","no-cache");
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq("http://foo.example.com:80/"), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariantURIsAreFlushedAlso() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/");
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final String variantUri = "theVariantURI";
|
||||
final Map<String,String> mapOfURIs = HttpTestUtils.makeDefaultVariantMap(variantUri, variantUri);
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
cacheEntryHasVariantMap(mapOfURIs);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(variantUri), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushForResponsesWithoutContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(201);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryForUnsuccessfulResponse() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request");
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByNonCanonicalContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", "http://foo.example.com/bar");
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByRelativeContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", "/bar");
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryIfContentLocationFromDifferentHost() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://baz.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfEtagsMatch() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"same-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"same-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfOlder() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(now)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryIfNotInCache() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
cacheReturnsEntryForUri(key, null);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.removeHeaders("ETag");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag", "\"some-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag", "\"new-etag\"");
|
||||
response.removeHeaders("Date");
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", "blarg");
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", "foo")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verify(mockStorage).removeEntry(ArgumentMatchers.eq(key), ArgumentMatchers.any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
|
||||
// Expectations
|
||||
private void cacheEntryHasVariantMap(final Map<String,String> variantMap) {
|
||||
when(mockEntry.getVariantMap()).thenReturn(variantMap);
|
||||
}
|
||||
|
||||
private void cacheReturnsEntryForUri(final String key, final HttpCacheEntry cacheEntry) {
|
||||
Mockito.when(mockStorage.getEntry(
|
||||
ArgumentMatchers.eq(key),
|
||||
ArgumentMatchers.any())).thenAnswer((Answer<Cancellable>) invocation -> {
|
||||
final FutureCallback<HttpCacheEntry> callback = invocation.getArgument(1);
|
||||
callback.completed(cacheEntry);
|
||||
return cancellable;
|
||||
});
|
||||
}
|
||||
|
||||
private void cacheEntryisForMethod(final String httpMethod) {
|
||||
when(mockEntry.getRequestMethod()).thenReturn(httpMethod);
|
||||
}
|
||||
}
|
|
@ -1,666 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
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.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.function.Resolver;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
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.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class TestDefaultCacheInvalidator {
|
||||
|
||||
private DefaultCacheInvalidator impl;
|
||||
private HttpHost host;
|
||||
@Mock
|
||||
private HttpCacheEntry mockEntry;
|
||||
@Mock
|
||||
private Resolver<URI, String> cacheKeyResolver;
|
||||
@Mock
|
||||
private HttpCacheStorage mockStorage;
|
||||
|
||||
private Instant now;
|
||||
private Instant tenSecondsAgo;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
now = Instant.now();
|
||||
tenSecondsAgo = now.minusSeconds(10);
|
||||
|
||||
when(cacheKeyResolver.resolve(ArgumentMatchers.any())).thenAnswer((Answer<String>) invocation -> {
|
||||
final URI uri = invocation.getArgument(0);
|
||||
return CacheSupport.normalize(uri).toASCIIString();
|
||||
});
|
||||
|
||||
host = new HttpHost("foo.example.com");
|
||||
impl = new DefaultCacheInvalidator();
|
||||
}
|
||||
|
||||
// Tests
|
||||
@Test
|
||||
public void testInvalidatesRequestsThatArentGETorHEAD() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/path");
|
||||
final String key = "http://foo.example.com:80/path";
|
||||
final Map<String,String> variantMap = new HashMap<>();
|
||||
cacheEntryHasVariantMap(variantMap);
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUrisInContentLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://foo.example.com/content";
|
||||
request.setHeader("Content-Location", contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verify(mockStorage).removeEntry("http://foo.example.com:80/content");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesUrisInLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://foo.example.com/content";
|
||||
request.setHeader("Location",contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verify(mockStorage).removeEntry("http://foo.example.com:80/content");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesRelativeUrisInContentLocationHeadersOnPUTs() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String relativePath = "/content";
|
||||
request.setHeader("Content-Location",relativePath);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verify(mockStorage).removeEntry("http://foo.example.com:80/content");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateUrisInContentLocationHeadersOnPUTsToDifferentHosts() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("PUT","/");
|
||||
request.setHeader("Content-Length","128");
|
||||
|
||||
final String contentLocation = "http://bar.example.com/content";
|
||||
request.setHeader("Content-Location",contentLocation);
|
||||
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry("http://foo.example.com:80/");
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD","/");
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry("http://foo.example.com:80/");
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesHEADCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
|
||||
cacheEntryisForMethod("HEAD");
|
||||
cacheEntryHasVariantMap(new HashMap<>());
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesVariantHEADCacheEntriesIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
final String theVariantKey = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}";
|
||||
final String theVariantURI = "{Accept-Encoding=gzip%2Cdeflate&User-Agent=Apache-HttpClient}http://foo.example.com:80/";
|
||||
final Map<String, String> variants = HttpTestUtils.makeDefaultVariantMap(theVariantKey, theVariantURI);
|
||||
|
||||
cacheEntryisForMethod("HEAD");
|
||||
cacheEntryHasVariantMap(variants);
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verify(mockStorage).removeEntry(theVariantURI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADCacheEntry() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD", uri);
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADCacheEntryIfSubsequentHEADRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD", uri);
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETCacheEntryIfSubsequentGETRequestsAreMadeToTheSameURI() throws Exception {
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final HttpRequest request = new BasicHttpRequest("GET", uri);
|
||||
|
||||
cacheEntryisForMethod("GET");
|
||||
cacheReturnsEntryForUri(key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientCacheControlHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Cache-Control","no-cache");
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry("http://foo.example.com:80/");
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientPragmaHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Pragma","no-cache");
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry("http://foo.example.com:80/");
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariantURIsAreFlushedAlso() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/");
|
||||
final URI uri = new URI("http://foo.example.com:80/");
|
||||
final String key = uri.toASCIIString();
|
||||
final String variantUri = "theVariantURI";
|
||||
final Map<String,String> mapOfURIs = HttpTestUtils.makeDefaultVariantMap(variantUri, variantUri);
|
||||
|
||||
cacheReturnsEntryForUri(key);
|
||||
cacheEntryHasVariantMap(mapOfURIs);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByRequest(host, request, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).removeEntry(variantUri);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushForResponsesWithoutContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(201);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryForUnsuccessfulResponse() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request");
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByNonCanonicalContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String cacheKey = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", "http://foo.example.com/bar");
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(cacheKey)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(cacheKey);
|
||||
verify(mockStorage).removeEntry(cacheKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntryIfFresherAndSpecifiedByRelativeContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String cacheKey = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", "/bar");
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(cacheKey)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(cacheKey);
|
||||
verify(mockStorage).removeEntry(cacheKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryIfContentLocationFromDifferentHost() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String cacheKey = "http://baz.example.com:80/bar";
|
||||
response.setHeader("Content-Location", cacheKey);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfEtagsMatch() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"same-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"same-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfOlder() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(now)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntryIfNotInCache() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(null);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.removeHeaders("ETag");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoEtag() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag", "\"some-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfResponseHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag", "\"new-etag\"");
|
||||
response.removeHeaders("Date");
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfEntryHasNoDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfResponseHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", "blarg");
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo))
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flushesEntrySpecifiedByContentLocationIfEntryHasMalformedDate() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response.setHeader("ETag","\"new-etag\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("ETag", "\"old-etag\""),
|
||||
new BasicHeader("Date", "foo")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(key)).thenReturn(entry);
|
||||
|
||||
impl.flushCacheEntriesInvalidatedByExchange(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
|
||||
// Expectations
|
||||
private void cacheEntryHasVariantMap(final Map<String,String> variantMap) {
|
||||
when(mockEntry.getVariantMap()).thenReturn(variantMap);
|
||||
}
|
||||
|
||||
private void cacheReturnsEntryForUri(final String key) throws IOException {
|
||||
when(mockStorage.getEntry(key)).thenReturn(mockEntry);
|
||||
}
|
||||
|
||||
private void cacheEntryisForMethod(final String httpMethod) {
|
||||
when(mockEntry.getRequestMethod()).thenReturn(httpMethod);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue