HTTPCLIENT-1824: asynchronous HTTP cache invalidator

This commit is contained in:
Oleg Kalnichevski 2017-12-17 14:34:20 +01:00
parent 6200a17c0c
commit 3f52d0bf90
7 changed files with 1170 additions and 120 deletions

View 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);
}

View 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);
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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", "/");