HTTPCLIENT-1824: asynchronous HTTP cache invalidator
This commit is contained in:
parent
6200a17c0c
commit
3f52d0bf90
84
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java
vendored
Normal file
84
httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
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.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
|
||||
/**
|
||||
* Given a particular HTTP request / response pair, flush any cache entries
|
||||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Internal
|
||||
public interface HttpAsyncCacheInvalidator {
|
||||
|
||||
/**
|
||||
* Remove cache entries from the cache that are no longer fresh or have been
|
||||
* invalidated in some way.
|
||||
*
|
||||
* @param host backend host
|
||||
* @param request request message
|
||||
* @param cacheKeyResolver cache key resolver used by cache storage
|
||||
* @param cacheStorage internal cache storage
|
||||
* @param callback result callback
|
||||
*/
|
||||
Cancellable flushInvalidatedCacheEntries(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
Resolver<URI, String> cacheKeyResolver,
|
||||
HttpAsyncCacheStorage cacheStorage,
|
||||
FutureCallback<Boolean> callback);
|
||||
|
||||
/**
|
||||
* Flushes entries that were invalidated by the given response received for
|
||||
* the given host/request pair.
|
||||
*
|
||||
* @param host backend host
|
||||
* @param request request message
|
||||
* @param response response message
|
||||
* @param cacheKeyResolver cache key resolver used by cache storage
|
||||
* @param cacheStorage internal cache storage
|
||||
* @param callback result callback
|
||||
*/
|
||||
Cancellable flushInvalidatedCacheEntries(
|
||||
HttpHost host,
|
||||
HttpRequest request,
|
||||
HttpResponse response,
|
||||
Resolver<URI, String> cacheKeyResolver,
|
||||
HttpAsyncCacheStorage cacheStorage,
|
||||
FutureCallback<Boolean> callback);
|
||||
|
||||
}
|
105
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
vendored
Normal file
105
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.HeaderConstants;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
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;
|
||||
|
||||
class CacheInvalidatorBase {
|
||||
|
||||
static boolean shouldInvalidateHeadCacheEntry(final HttpRequest req, final HttpCacheEntry parentCacheEntry) {
|
||||
return requestIsGet(req) && isAHeadCacheEntry(parentCacheEntry);
|
||||
}
|
||||
|
||||
static boolean requestIsGet(final HttpRequest req) {
|
||||
return req.getMethod().equals((HeaderConstants.GET_METHOD));
|
||||
}
|
||||
|
||||
static boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) {
|
||||
return parentCacheEntry != null && parentCacheEntry.getRequestMethod().equals(HeaderConstants.HEAD_METHOD);
|
||||
}
|
||||
|
||||
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 !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
|
||||
.equals(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 = HttpCacheSupport.normalizeQuetly(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(HeaderConstants.ETAG);
|
||||
final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (entryEtag == null || responseEtag == null) {
|
||||
return false;
|
||||
}
|
||||
return (!entryEtag.getValue().equals(responseEtag.getValue()));
|
||||
}
|
||||
|
||||
static boolean responseDateOlderThanEntryDate(final HttpResponse response, final HttpCacheEntry entry) {
|
||||
return DateUtils.isBefore(response, entry, HttpHeaders.DATE);
|
||||
}
|
||||
|
||||
}
|
|
@ -100,7 +100,7 @@ public class CachingExecBase {
|
|||
this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
|
||||
}
|
||||
|
||||
public CachingExecBase(final HttpCache cache, final CacheConfig config) {
|
||||
CachingExecBase(final HttpCache cache, final CacheConfig config) {
|
||||
super();
|
||||
this.responseCache = Args.notNull(cache, "Response cache");
|
||||
this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.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.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Given a particular HTTP request / response pair, flush any cache entries
|
||||
* that this exchange would invalidate.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
@Internal
|
||||
public class DefaultAsyncCacheInvalidator extends CacheInvalidatorBase implements HttpAsyncCacheInvalidator {
|
||||
|
||||
public static final DefaultAsyncCacheInvalidator INSTANCE = new DefaultAsyncCacheInvalidator();
|
||||
|
||||
private final Logger log = LogManager.getLogger(getClass());
|
||||
|
||||
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) {
|
||||
log.debug("Cache entry with key " + cacheKey + " successfully flushed");
|
||||
} else {
|
||||
log.debug("Cache entry with key " + cacheKey + " could not be flushed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 flushInvalidatedCacheEntries(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpAsyncCacheStorage storage,
|
||||
final FutureCallback<Boolean> callback) {
|
||||
final String s = HttpCacheSupport.getRequestUri(request, host);
|
||||
final URI uri = HttpCacheSupport.normalizeQuetly(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(s + " is not a valid URI");
|
||||
}
|
||||
final Header clHdr = request.getFirstHeader("Content-Location");
|
||||
if (clHdr != null) {
|
||||
final URI contentLocation = HttpCacheSupport.normalizeQuetly(clHdr.getValue());
|
||||
if (contentLocation != null) {
|
||||
if (!flushAbsoluteUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage)) {
|
||||
flushRelativeUriFromSameHost(uri, contentLocation, cacheKeyResolver, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
final Header lHdr = request.getFirstHeader("Location");
|
||||
if (lHdr != null) {
|
||||
final URI location = HttpCacheSupport.normalizeQuetly(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;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable flushInvalidatedCacheEntries(
|
||||
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 = HttpCacheSupport.getRequestUri(request, host);
|
||||
final URI requestUri = HttpCacheSupport.normalizeQuetly(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();
|
||||
}
|
||||
|
||||
}
|
|
@ -28,19 +28,16 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HeaderConstants;
|
||||
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.DateUtils;
|
||||
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;
|
||||
|
@ -55,7 +52,7 @@ import org.apache.logging.log4j.Logger;
|
|||
*/
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
@Internal
|
||||
public class DefaultCacheInvalidator implements HttpCacheInvalidator {
|
||||
public class DefaultCacheInvalidator extends CacheInvalidatorBase implements HttpCacheInvalidator {
|
||||
|
||||
public static final DefaultCacheInvalidator INSTANCE = new DefaultCacheInvalidator();
|
||||
|
||||
|
@ -82,13 +79,6 @@ public class DefaultCacheInvalidator implements HttpCacheInvalidator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cache entries from the cache that are no longer fresh or
|
||||
* have been invalidated in some way.
|
||||
*
|
||||
* @param host The backend host we are talking to
|
||||
* @param request The HttpRequest to that host
|
||||
*/
|
||||
@Override
|
||||
public void flushInvalidatedCacheEntries(
|
||||
final HttpHost host,
|
||||
|
@ -134,66 +124,30 @@ public class DefaultCacheInvalidator implements HttpCacheInvalidator {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean shouldInvalidateHeadCacheEntry(final HttpRequest req, final HttpCacheEntry parentCacheEntry) {
|
||||
return requestIsGet(req) && isAHeadCacheEntry(parentCacheEntry);
|
||||
}
|
||||
|
||||
private boolean requestIsGet(final HttpRequest req) {
|
||||
return req.getMethod().equals((HeaderConstants.GET_METHOD));
|
||||
}
|
||||
|
||||
private boolean isAHeadCacheEntry(final HttpCacheEntry parentCacheEntry) {
|
||||
return parentCacheEntry != null && parentCacheEntry.getRequestMethod().equals(HeaderConstants.HEAD_METHOD);
|
||||
}
|
||||
|
||||
private void flushUriIfSameHost(
|
||||
final URI requestURI,
|
||||
final URI targetURI,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
if (targetURI.isAbsolute() && targetURI.getAuthority().equalsIgnoreCase(requestURI.getAuthority())) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(targetURI));
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
flushUriIfSameHost(requestUri, resolvedUri, cacheKeyResolver, storage);
|
||||
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 && uri.isAbsolute()) {
|
||||
flushUriIfSameHost(requestUri, uri, cacheKeyResolver, storage);
|
||||
if (uri != null && isSameHost(requestUri, uri)) {
|
||||
removeEntry(storage, cacheKeyResolver.resolve(uri));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestShouldNotBeCached(final HttpRequest req) {
|
||||
final String method = req.getMethod();
|
||||
return notGetOrHeadRequest(method);
|
||||
}
|
||||
|
||||
private boolean notGetOrHeadRequest(final String method) {
|
||||
return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
|
||||
.equals(method));
|
||||
}
|
||||
|
||||
/** Flushes entries that were invalidated by the given response
|
||||
* received for the given host/request pair.
|
||||
*/
|
||||
@Override
|
||||
public void flushInvalidatedCacheEntries(
|
||||
final HttpHost host,
|
||||
|
@ -211,75 +165,30 @@ public class DefaultCacheInvalidator implements HttpCacheInvalidator {
|
|||
return;
|
||||
}
|
||||
final URI contentLocation = getContentLocationURI(uri, response);
|
||||
if (contentLocation != null) {
|
||||
flushLocationCacheEntry(uri, response, contentLocation, cacheKeyResolver, storage);
|
||||
if (contentLocation != null && isSameHost(uri, contentLocation)) {
|
||||
flushLocationCacheEntry(response, contentLocation, storage, cacheKeyResolver);
|
||||
}
|
||||
final URI location = getLocationURI(uri, response);
|
||||
if (location != null) {
|
||||
flushLocationCacheEntry(uri, response, location, cacheKeyResolver, storage);
|
||||
if (location != null && isSameHost(uri, location)) {
|
||||
flushLocationCacheEntry(response, location, storage, cacheKeyResolver);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushLocationCacheEntry(
|
||||
final URI requestUri,
|
||||
final HttpResponse response,
|
||||
final URI location,
|
||||
final Resolver<URI, String> cacheKeyResolver,
|
||||
final HttpCacheStorage storage) {
|
||||
final HttpCacheStorage storage,
|
||||
final Resolver<URI, String> cacheKeyResolver) {
|
||||
final String cacheKey = cacheKeyResolver.resolve(location);
|
||||
final HttpCacheEntry entry = getEntry(storage, cacheKey);
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
if (entry != null) {
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
|
||||
if (responseDateOlderThanEntryDate(response, entry)) {
|
||||
return;
|
||||
}
|
||||
if (!responseAndEntryEtagsDiffer(response, entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushUriIfSameHost(requestUri, location, cacheKeyResolver, storage);
|
||||
}
|
||||
|
||||
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 = HttpCacheSupport.normalizeQuetly(h.getValue());
|
||||
if (locationUri == null) {
|
||||
return requestUri;
|
||||
}
|
||||
if (locationUri.isAbsolute()) {
|
||||
return locationUri;
|
||||
} else {
|
||||
return URIUtils.resolve(requestUri, locationUri);
|
||||
if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
|
||||
removeEntry(storage, cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private URI getContentLocationURI(final URI requestUri, final HttpResponse response) {
|
||||
return getLocationURI(requestUri, response, HttpHeaders.CONTENT_LOCATION);
|
||||
}
|
||||
|
||||
private URI getLocationURI(final URI requestUri, final HttpResponse response) {
|
||||
return getLocationURI(requestUri, response, HttpHeaders.LOCATION);
|
||||
}
|
||||
|
||||
private boolean responseAndEntryEtagsDiffer(final HttpResponse response,
|
||||
final HttpCacheEntry entry) {
|
||||
final Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG);
|
||||
final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (entryEtag == null || responseEtag == null) {
|
||||
return false;
|
||||
}
|
||||
return (!entryEtag.getValue().equals(responseEtag.getValue()));
|
||||
}
|
||||
|
||||
private boolean responseDateOlderThanEntryDate(final HttpResponse response, final HttpCacheEntry entry) {
|
||||
return DateUtils.isBefore(response, entry, HttpHeaders.DATE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.util.Date;
|
||||
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.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
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 Date now;
|
||||
private Date tenSecondsAgo;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
now = new Date();
|
||||
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
||||
when(cacheKeyResolver.resolve(Mockito.<URI>any())).thenAnswer(new Answer<String>() {
|
||||
|
||||
@Override
|
||||
public String answer(final InvocationOnMock invocation) throws Throwable {
|
||||
final URI uri = invocation.getArgument(0);
|
||||
return HttpCacheSupport.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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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<String,String>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq("http://foo.example.com:80/content"), Mockito.<FutureCallback<Boolean>>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<String,String>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq("http://foo.example.com:80/content"), Mockito.<FutureCallback<Boolean>>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<String,String>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq("http://foo.example.com:80/content"), Mockito.<FutureCallback<Boolean>>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<String,String>());
|
||||
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq("http://foo.example.com:80/"), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADRequest() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("HEAD","/");
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq("http://foo.example.com:80/"), Mockito.<FutureCallback<HttpCacheEntry>>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<String, String>());
|
||||
cacheReturnsEntryForUri(key, mockEntry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(theVariantURI), Mockito.<FutureCallback<Boolean>>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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockEntry).getRequestMethod();
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientCacheControlHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Cache-Control","no-cache");
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq("http://foo.example.com:80/"), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateRequestsWithClientPragmaHeaders() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET","/");
|
||||
request.setHeader("Pragma","no-cache");
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq("http://foo.example.com:80/"), Mockito.<FutureCallback<HttpCacheEntry>>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.flushInvalidatedCacheEntries(host, request, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(variantUri), Mockito.<FutureCallback<Boolean>>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotFlushForResponsesWithoutContentLocation() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("POST","/");
|
||||
final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
impl.flushInvalidatedCacheEntries(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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"same-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.formatDate(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.formatDate(now)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.formatDate(now));
|
||||
final String key = "http://foo.example.com:80/bar";
|
||||
response.setHeader("Content-Location", key);
|
||||
|
||||
cacheReturnsEntryForUri(key, null);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.formatDate(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.formatDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>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.formatDate(tenSecondsAgo)),
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(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.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(tenSecondsAgo))
|
||||
});
|
||||
|
||||
cacheReturnsEntryForUri(key, entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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.formatDate(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.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage, operationCallback);
|
||||
|
||||
verify(mockStorage).getEntry(Mockito.eq(key), Mockito.<FutureCallback<HttpCacheEntry>>any());
|
||||
verify(mockStorage).removeEntry(Mockito.eq(key), Mockito.<FutureCallback<Boolean>>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(
|
||||
Mockito.eq(key),
|
||||
Mockito.<FutureCallback<HttpCacheEntry>>any())).thenAnswer(new Answer<Cancellable>() {
|
||||
|
||||
@Override
|
||||
public Cancellable answer(final InvocationOnMock invocation) throws Throwable {
|
||||
final FutureCallback<HttpCacheEntry> callback = invocation.getArgument(1);
|
||||
callback.completed(cacheEntry);
|
||||
return cancellable;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void cacheEntryisForMethod(final String httpMethod) {
|
||||
when(mockEntry.getRequestMethod()).thenReturn(httpMethod);
|
||||
}
|
||||
}
|
|
@ -150,7 +150,7 @@ public class TestDefaultCacheInvalidator {
|
|||
verify(mockEntry).getVariantMap();
|
||||
verify(mockStorage).getEntry(key);
|
||||
verify(mockStorage).removeEntry(key);
|
||||
verify(mockStorage).removeEntry(cacheKeyResolver.resolve(new URI(contentLocation)));
|
||||
verify(mockStorage).removeEntry("http://foo.example.com:80/content");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -459,21 +459,11 @@ public class TestDefaultCacheInvalidator {
|
|||
final String cacheKey = "http://baz.example.com:80/bar";
|
||||
response.setHeader("Content-Location", cacheKey);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("ETag", "\"old-etag\"")
|
||||
});
|
||||
|
||||
when(mockStorage.getEntry(cacheKey)).thenReturn(entry);
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, request, response, cacheKeyResolver, mockStorage);
|
||||
|
||||
verify(mockStorage).getEntry(cacheKey);
|
||||
verifyNoMoreInteractions(mockStorage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void doesNotFlushEntrySpecifiedByContentLocationIfEtagsMatch() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
|
|
Loading…
Reference in New Issue