You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2017/12/27 10:55:46 UTC
[2/4] httpcomponents-client git commit: HTTPCLIENT-1824: asynchronous
HTTP cache invalidator
HTTPCLIENT-1824: asynchronous HTTP cache invalidator
Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/3f52d0bf
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/3f52d0bf
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/3f52d0bf
Branch: refs/heads/master
Commit: 3f52d0bf90c4d9ec09cf8e8a3d583cea05f94b0e
Parents: 6200a17
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sun Dec 17 14:34:20 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Tue Dec 26 18:12:18 2017 +0100
----------------------------------------------------------------------
.../http/cache/HttpAsyncCacheInvalidator.java | 84 +++
.../http/impl/cache/CacheInvalidatorBase.java | 105 +++
.../http/impl/cache/CachingExecBase.java | 2 +-
.../cache/DefaultAsyncCacheInvalidator.java | 266 +++++++
.../impl/cache/DefaultCacheInvalidator.java | 125 +---
.../cache/TestDefaultAsyncCacheInvalidator.java | 696 +++++++++++++++++++
.../impl/cache/TestDefaultCacheInvalidator.java | 12 +-
7 files changed, 1170 insertions(+), 120 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java
new file mode 100644
index 0000000..fa9684b
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpAsyncCacheInvalidator.java
@@ -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);
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
new file mode 100644
index 0000000..d229011
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheInvalidatorBase.java
@@ -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);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index 39300f3..068432b 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -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;
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheInvalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheInvalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheInvalidator.java
new file mode 100644
index 0000000..c815ce6
--- /dev/null
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheInvalidator.java
@@ -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();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheInvalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheInvalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheInvalidator.java
index 029928b..76ac99e 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheInvalidator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheInvalidator.java
@@ -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;
- }
-
- // do not invalidate if response is strictly older than entry
- // or if the etags match
+ if (entry != null) {
+ // 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);
- }
- }
-
- 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;
+ if (!responseDateOlderThanEntryDate(response, entry) && responseAndEntryEtagsDiffer(response, entry)) {
+ removeEntry(storage, cacheKey);
+ }
}
- return (!entryEtag.getValue().equals(responseEtag.getValue()));
}
- private boolean responseDateOlderThanEntryDate(final HttpResponse response, final HttpCacheEntry entry) {
- return DateUtils.isBefore(response, entry, HttpHeaders.DATE);
- }
}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultAsyncCacheInvalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultAsyncCacheInvalidator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultAsyncCacheInvalidator.java
new file mode 100644
index 0000000..d64018d
--- /dev/null
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultAsyncCacheInvalidator.java
@@ -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);
+ }
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/3f52d0bf/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultCacheInvalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultCacheInvalidator.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultCacheInvalidator.java
index 5c18134..31f9af2 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultCacheInvalidator.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestDefaultCacheInvalidator.java
@@ -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", "/");