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 2024/01/18 19:08:45 UTC
(httpcomponents-client) branch master updated (a1fa1739b -> a1e8e9082)
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
from a1fa1739b Fixed a design mistake of ConnectionEndpoint having a direct dependency on HttpRequestExecutor class
new 07586902e ETag API
new a1e8e9082 Better ETag handling
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../hc/client5/http/cache/HttpCacheEntry.java | 22 +++
.../client5/http/impl/cache/AsyncCachingExec.java | 20 +--
.../http/impl/cache/BasicHttpAsyncCache.java | 9 +-
.../hc/client5/http/impl/cache/BasicHttpCache.java | 9 +-
.../impl/cache/CachedHttpResponseGenerator.java | 9 +-
.../cache/CachedResponseSuitabilityChecker.java | 15 +-
.../hc/client5/http/impl/cache/CachingExec.java | 17 +-
.../http/impl/cache/ConditionalRequestBuilder.java | 25 ++-
.../http/impl/cache/TestCachingExecChain.java | 25 +--
.../impl/cache/TestConditionalRequestBuilder.java | 64 +++----
.../hc/client5/http/protocol/RequestIfRange.java | 6 +-
.../org/apache/hc/client5/http/validator/ETag.java | 192 +++++++++++++++++++++
.../hc/client5/http/validator/ValidatorType.java | 10 +-
.../apache/hc/client5/http/validator/TestETag.java | 93 ++++++++++
14 files changed, 424 insertions(+), 92 deletions(-)
create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
copy httpclient5-cache/src/main/java/org/apache/hc/client5/http/schedule/package-info.java => httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java (91%)
create mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java
(httpcomponents-client) 02/02: Better ETag handling
Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
commit a1e8e9082e72347e0488ea4ac793a2d412aba8cc
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Tue Jan 9 14:30:04 2024 +0100
Better ETag handling
---
.../hc/client5/http/cache/HttpCacheEntry.java | 22 ++++++++
.../client5/http/impl/cache/AsyncCachingExec.java | 20 +++----
.../http/impl/cache/BasicHttpAsyncCache.java | 9 ++-
.../hc/client5/http/impl/cache/BasicHttpCache.java | 9 ++-
.../impl/cache/CachedHttpResponseGenerator.java | 9 +--
.../cache/CachedResponseSuitabilityChecker.java | 15 ++---
.../hc/client5/http/impl/cache/CachingExec.java | 17 +++---
.../http/impl/cache/ConditionalRequestBuilder.java | 25 +++++++--
.../http/impl/cache/TestCachingExecChain.java | 25 +++++----
.../impl/cache/TestConditionalRequestBuilder.java | 64 ++++++++++++----------
.../hc/client5/http/protocol/RequestIfRange.java | 6 +-
.../org/apache/hc/client5/http/validator/ETag.java | 1 +
12 files changed, 132 insertions(+), 90 deletions(-)
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
index ffaf52766..e69eb185f 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/cache/HttpCacheEntry.java
@@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
@@ -79,6 +80,8 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
private final AtomicReference<Instant> expiresRef;
private final AtomicReference<Instant> lastModifiedRef;
+ private final AtomicReference<ETag> eTagRef;
+
/**
* Internal constructor that makes no validation of the input parameters and makes
* no copies of the original client request and the origin response.
@@ -107,6 +110,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.dateRef = new AtomicReference<>();
this.expiresRef = new AtomicReference<>();
this.lastModifiedRef = new AtomicReference<>();
+ this.eTagRef = new AtomicReference<>();
}
/**
@@ -179,6 +183,7 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.dateRef = new AtomicReference<>();
this.expiresRef = new AtomicReference<>();
this.lastModifiedRef = new AtomicReference<>();
+ this.eTagRef = new AtomicReference<>();
}
/**
@@ -396,6 +401,23 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
return getInstant(lastModifiedRef, HttpHeaders.LAST_MODIFIED);
}
+ /**
+ * @since 5.4
+ */
+ public ETag getETag() {
+ ETag eTag = eTagRef.get();
+ if (eTag == null) {
+ eTag = ETag.get(this);
+ if (eTag == null) {
+ return null;
+ }
+ if (!eTagRef.compareAndSet(null, eTag)) {
+ eTag = eTagRef.get();
+ }
+ }
+ return eTag;
+ }
+
/**
* Returns the {@link Resource} containing the origin response body.
*/
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 8386558a0..ffd9b261d 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -30,7 +30,6 @@ import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -55,6 +54,7 @@ import org.apache.hc.client5.http.cache.ResponseCacheControl;
import org.apache.hc.client5.http.impl.ExecSupport;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.schedule.SchedulingStrategy;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.CancellableDependency;
@@ -1206,16 +1206,17 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
final Collection<CacheHit> variants) {
final String exchangeId = scope.exchangeId;
final CancellableDependency operation = scope.cancellableDependency;
- final Map<String, CacheHit> variantMap = new HashMap<>();
+ final Map<ETag, CacheHit> variantMap = new HashMap<>();
for (final CacheHit variant : variants) {
- final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
- if (header != null) {
- variantMap.put(header.getValue(), variant);
+ final ETag eTag = variant.entry.getETag();
+ if (eTag != null) {
+ variantMap.put(eTag, variant);
}
}
+
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
- BasicRequestBuilder.copy(request).build(),
- new ArrayList<>(variantMap.keySet()));
+ request,
+ variantMap.keySet());
final Instant requestDate = getCurrentDate();
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
@@ -1270,14 +1271,13 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
} else {
- final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
- if (resultEtagHeader == null) {
+ final ETag resultEtag = ETag.get(backendResponse);
+ if (resultEtag == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} 304 response did not contain ETag", exchangeId);
}
callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
} else {
- final String resultEtag = resultEtagHeader.getValue();
final CacheHit match = variantMap.get(resultEtag);
if (match == null) {
if (LOG.isDebugEnabled()) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
index e0da6e04f..55af33e17 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
@@ -33,7 +33,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -46,11 +45,11 @@ import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.impl.Operations;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.concurrent.CallbackContribution;
import org.apache.hc.core5.concurrent.Cancellable;
import org.apache.hc.core5.concurrent.ComplexCancellable;
import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
@@ -520,10 +519,10 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
if (LOG.isDebugEnabled()) {
LOG.debug("Evicting root cache entry {}", rootKey);
}
- final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
- final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
+ final ETag existingETag = root.getETag();
+ final ETag newETag = ETag.get(response);
if (existingETag != null && newETag != null &&
- !Objects.equals(existingETag.getValue(), newETag.getValue()) &&
+ !ETag.strongCompare(existingETag, newETag) &&
!HttpCacheEntry.isNewer(root, response)) {
evictAll(root, rootKey);
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
index b63561068..274f099c1 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
@@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
@@ -43,7 +42,7 @@ import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
-import org.apache.hc.core5.http.Header;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
@@ -329,10 +328,10 @@ class BasicHttpCache implements HttpCache {
if (root == null) {
return;
}
- final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
- final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
+ final ETag existingETag = root.getETag();
+ final ETag newETag = ETag.get(response);
if (existingETag != null && newETag != null &&
- !Objects.equals(existingETag.getValue(), newETag.getValue()) &&
+ !ETag.strongCompare(existingETag, newETag) &&
!HttpCacheEntry.isNewer(root, response)) {
evictAll(root, rootKey);
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
index 9002809be..7a4d03dc9 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedHttpResponseGenerator.java
@@ -33,6 +33,7 @@ import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.Resource;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
@@ -108,9 +109,9 @@ class CachedHttpResponseGenerator {
// - ETag and/or Content-Location, if the header would have been sent
// in a 200 response to the same request
- final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
- if (etagHeader != null) {
- response.addHeader(etagHeader);
+ final ETag eTag = entry.getETag();
+ if (eTag != null) {
+ response.addHeader(new BasicHeader(HttpHeaders.ETAG, eTag.toString()));
}
final Header contentLocationHeader = entry.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
@@ -142,7 +143,7 @@ class CachedHttpResponseGenerator {
//above listed fields unless said metadata exists for the purpose of
//guiding cache updates (e.g., Last-Modified might be useful if the
//response does not have an ETag field).
- if (etagHeader == null) {
+ if (eTag == null) {
final Header lastModifiedHeader = entry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
if (lastModifiedHeader != null) {
response.addHeader(lastModifiedHeader);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
index 2eccbcf69..c7c124997 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachedResponseSuitabilityChecker.java
@@ -41,8 +41,8 @@ import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.RequestCacheControl;
import org.apache.hc.client5.http.cache.ResponseCacheControl;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpStatus;
@@ -332,13 +332,14 @@ class CachedResponseSuitabilityChecker {
* @return boolean does the etag validator match
*/
boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) {
- final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
- final String etag = (etagHeader != null) ? etagHeader.getValue() : null;
- final Iterator<HeaderElement> it = MessageSupport.iterate(request, HttpHeaders.IF_NONE_MATCH);
+ final ETag etag = entry.getETag();
+ if (etag == null) {
+ return false;
+ }
+ final Iterator<String> it = MessageSupport.iterateTokens(request, HttpHeaders.IF_NONE_MATCH);
while (it.hasNext()) {
- final HeaderElement elt = it.next();
- final String reqEtag = elt.toString();
- if (("*".equals(reqEtag) && etag != null) || reqEtag.equals(etag)) {
+ final String token = it.next();
+ if ("*".equals(token) || ETag.weakCompare(etag, ETag.parse(token))) {
return true;
}
}
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 6ce584945..fdae58507 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -49,6 +48,7 @@ import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.impl.ExecSupport;
import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
@@ -605,17 +605,17 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
final List<CacheHit> variants) throws IOException, HttpException {
final String exchangeId = scope.exchangeId;
- final Map<String, CacheHit> variantMap = new HashMap<>();
+ final Map<ETag, CacheHit> variantMap = new HashMap<>();
for (final CacheHit variant : variants) {
- final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
- if (header != null) {
- variantMap.put(header.getValue(), variant);
+ final ETag eTag = variant.entry.getETag();
+ if (eTag != null) {
+ variantMap.put(eTag, variant);
}
}
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
request,
- new ArrayList<>(variantMap.keySet()));
+ variantMap.keySet());
final Instant requestDate = getCurrentDate();
final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
@@ -629,15 +629,14 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
backendResponse.close();
}
- final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
- if (resultEtagHeader == null) {
+ final ETag resultEtag = ETag.get(backendResponse);
+ if (resultEtag == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} 304 response did not contain ETag", exchangeId);
}
return callBackend(target, request, scope, chain);
}
- final String resultEtag = resultEtagHeader.getValue();
final CacheHit match = variantMap.get(resultEtag);
if (match == null) {
if (LOG.isDebugEnabled()) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
index b262673fa..ac0aec605 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ConditionalRequestBuilder.java
@@ -26,16 +26,18 @@
*/
package org.apache.hc.client5.http.impl.cache;
-import java.util.List;
+import java.util.Collection;
import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.ResponseCacheControl;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.function.Factory;
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.message.MessageSupport;
+import org.apache.hc.core5.http.message.BufferedHeader;
+import org.apache.hc.core5.util.CharArrayBuffer;
class ConditionalRequestBuilder<T extends HttpRequest> {
@@ -59,9 +61,9 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
public T buildConditionalRequest(final ResponseCacheControl cacheControl, final T request, final HttpCacheEntry cacheEntry) {
final T newRequest = messageCopier.create(request);
- final Header eTag = cacheEntry.getFirstHeader(HttpHeaders.ETAG);
+ final ETag eTag = cacheEntry.getETag();
if (eTag != null) {
- newRequest.setHeader(HttpHeaders.IF_NONE_MATCH, eTag.getValue());
+ newRequest.setHeader(HttpHeaders.IF_NONE_MATCH, eTag.toString());
}
final Header lastModified = cacheEntry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
if (lastModified != null) {
@@ -85,9 +87,20 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
* @param variants
* @return the wrapped request
*/
- public T buildConditionalRequestFromVariants(final T request, final List<String> variants) {
+ public T buildConditionalRequestFromVariants(final T request, final Collection<ETag> variants) {
final T newRequest = messageCopier.create(request);
- newRequest.setHeader(MessageSupport.headerOfTokens(HttpHeaders.IF_NONE_MATCH, variants));
+ final CharArrayBuffer buffer = new CharArrayBuffer(256);
+ buffer.append(HttpHeaders.IF_NONE_MATCH);
+ buffer.append(": ");
+ int i = 0;
+ for (final ETag variant : variants) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ variant.format(buffer);
+ i++;
+ }
+ newRequest.setHeader(BufferedHeader.create(buffer));
return newRequest;
}
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
index 4d96eedc5..e40ab6782 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
@@ -50,6 +50,7 @@ import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpOptions;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
@@ -997,20 +998,20 @@ public class TestCachingExecChain {
final Instant now = Instant.now();
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
- req1.addHeader("If-None-Match", "etag");
+ req1.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
- req2.addHeader("If-None-Match", "etag");
+ req2.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-Control", "max-age=1");
- resp1.setHeader("Etag", "etag");
+ resp1.setHeader("Etag", "\"etag\"");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
resp2.setHeader("Cache-Control", "max-age=1");
- resp1.setHeader("Etag", "etag");
+ resp1.setHeader("Etag", "\"etag\"");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
@@ -1020,9 +1021,9 @@ public class TestCachingExecChain {
final ClassicHttpResponse result2 = execute(req2);
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
- Assertions.assertEquals("etag", result1.getFirstHeader("Etag").getValue());
+ Assertions.assertEquals(new ETag("etag"), ETag.get(result1));
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
- Assertions.assertEquals("etag", result2.getFirstHeader("Etag").getValue());
+ Assertions.assertEquals(new ETag("etag"), ETag.get(result2));
}
@Test
@@ -1031,21 +1032,21 @@ public class TestCachingExecChain {
final Instant now = Instant.now();
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
- req1.addHeader("If-None-Match", "etag");
+ req1.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
- req2.addHeader("If-None-Match", "etag");
+ req2.addHeader("If-None-Match", "\"etag\"");
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-Control", "max-age=1");
- resp1.setHeader("Etag", "etag");
+ resp1.setHeader("Etag", "\"etag\"");
resp1.setHeader("Vary", "Accept-Encoding");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
resp2.setHeader("Cache-Control", "max-age=1");
- resp1.setHeader("Etag", "etag");
+ resp1.setHeader("Etag", "\"etag\"");
resp1.setHeader("Vary", "Accept-Encoding");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
@@ -1057,9 +1058,9 @@ public class TestCachingExecChain {
final ClassicHttpResponse result2 = execute(req2);
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
- Assertions.assertEquals("etag", result1.getFirstHeader("Etag").getValue());
+ Assertions.assertEquals(new ETag("etag"), ETag.get(result1));
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
- Assertions.assertEquals("etag", result2.getFirstHeader("Etag").getValue());
+ Assertions.assertEquals(new ETag("etag"), ETag.get(result2));
}
@Test
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
index 15bc20645..3daf75c51 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestConditionalRequestBuilder.java
@@ -27,19 +27,26 @@
package org.apache.hc.client5.http.impl.cache;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import org.apache.hc.client5.http.HeadersMatcher;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.RequestCacheControl;
import org.apache.hc.client5.http.cache.ResponseCacheControl;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
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.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.support.BasicRequestBuilder;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -76,11 +83,11 @@ public class TestConditionalRequestBuilder {
Assertions.assertEquals(theUri, newRequest.getRequestUri());
Assertions.assertEquals(2, newRequest.getHeaders().length);
- Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
- Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
-
- Assertions.assertEquals("If-Modified-Since", newRequest.getHeaders()[1].getName());
- Assertions.assertEquals(lastModified, newRequest.getHeaders()[1].getValue());
+ MatcherAssert.assertThat(
+ newRequest.getHeaders(),
+ HeadersMatcher.same(
+ new BasicHeader("Accept-Encoding", "gzip"),
+ new BasicHeader("If-Modified-Since", lastModified)));
}
@Test
@@ -110,14 +117,16 @@ public class TestConditionalRequestBuilder {
public void testBuildConditionalRequestWithETag() {
final String theMethod = "GET";
final String theUri = "/theuri";
- final String theETag = "this is my eTag";
+ final String theETag = "\"this is my eTag\"";
final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
basicRequest.addHeader("Accept-Encoding", "gzip");
+ final Instant now = Instant.now();
+
final Header[] headers = new Header[] {
- new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
- new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())),
+ new BasicHeader("Date", DateUtils.formatStandardDate(now)),
+ new BasicHeader("Last-Modified", DateUtils.formatStandardDate(now)),
new BasicHeader("ETag", theETag) };
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
@@ -128,13 +137,12 @@ public class TestConditionalRequestBuilder {
Assertions.assertEquals(theMethod, newRequest.getMethod());
Assertions.assertEquals(theUri, newRequest.getRequestUri());
- Assertions.assertEquals(3, newRequest.getHeaders().length);
-
- Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
- Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
-
- Assertions.assertEquals("If-None-Match", newRequest.getHeaders()[1].getName());
- Assertions.assertEquals(theETag, newRequest.getHeaders()[1].getValue());
+ MatcherAssert.assertThat(
+ newRequest.getHeaders(),
+ HeadersMatcher.same(
+ new BasicHeader("Accept-Encoding", "gzip"),
+ new BasicHeader("If-None-Match", theETag),
+ new BasicHeader("If-Modified-Since", DateUtils.formatStandardDate(now))));
}
@Test
@@ -260,25 +268,21 @@ public class TestConditionalRequestBuilder {
@Test
public void testBuildConditionalRequestFromVariants() throws Exception {
- final String etag1 = "\"123\"";
- final String etag2 = "\"456\"";
- final String etag3 = "\"789\"";
+ final ETag etag1 = new ETag("123");
+ final ETag etag2 = new ETag("456");
+ final ETag etag3 = new ETag("789");
- final List<String> variantEntries = Arrays.asList(etag1, etag2, etag3);
+ final List<ETag> variantEntries = Arrays.asList(etag1, etag2, etag3);
final HttpRequest conditional = impl.buildConditionalRequestFromVariants(request, variantEntries);
- // seems like a lot of work, but necessary, check for existence and exclusiveness
- String ifNoneMatch = conditional.getFirstHeader(HttpHeaders.IF_NONE_MATCH).getValue();
- Assertions.assertTrue(ifNoneMatch.contains(etag1));
- Assertions.assertTrue(ifNoneMatch.contains(etag2));
- Assertions.assertTrue(ifNoneMatch.contains(etag3));
- ifNoneMatch = ifNoneMatch.replace(etag1, "");
- ifNoneMatch = ifNoneMatch.replace(etag2, "");
- ifNoneMatch = ifNoneMatch.replace(etag3, "");
- ifNoneMatch = ifNoneMatch.replace(",","");
- ifNoneMatch = ifNoneMatch.replace(" ", "");
- Assertions.assertEquals(ifNoneMatch, "");
+
+ final Iterator<String> it = MessageSupport.iterateTokens(conditional, HttpHeaders.IF_NONE_MATCH);
+ final List<ETag> etags = new ArrayList<>();
+ while (it.hasNext()) {
+ etags.add(ETag.parse(it.next()));
+ }
+ MatcherAssert.assertThat(etags, Matchers.containsInAnyOrder(etag1, etag2, etag3));
}
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestIfRange.java b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestIfRange.java
index b39ac28c5..4caafd4bf 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestIfRange.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/protocol/RequestIfRange.java
@@ -33,6 +33,8 @@ import java.io.IOException;
import java.time.Instant;
import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.client5.http.validator.ETag;
+import org.apache.hc.client5.http.validator.ValidatorType;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.EntityDetails;
@@ -106,10 +108,10 @@ public class RequestIfRange implements HttpRequestInterceptor {
throw new ProtocolException("Request with 'If-Range' header must also contain a 'Range' header.");
}
- final Header eTag = request.getFirstHeader(HttpHeaders.ETAG);
+ final ETag eTag = ETag.get(request);
// If there's a weak ETag in the If-Range header, throw an exception
- if (eTag != null && eTag.getValue().startsWith("W/")) {
+ if (eTag != null && eTag.getType() == ValidatorType.WEAK) {
throw new ProtocolException("'If-Range' header must not contain a weak entity tag.");
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
index a5b32149c..d4894b265 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
@@ -31,6 +31,7 @@ import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
(httpcomponents-client) 01/02: ETag API
Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
commit 07586902ec316d3a772fc3f5a8416cee491ffeef
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Jan 8 16:05:23 2024 +0100
ETag API
---
.../org/apache/hc/client5/http/validator/ETag.java | 191 +++++++++++++++++++++
.../hc/client5/http/validator/ValidatorType.java | 37 ++++
.../apache/hc/client5/http/validator/TestETag.java | 93 ++++++++++
3 files changed, 321 insertions(+)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
new file mode 100644
index 000000000..a5b32149c
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ETag.java
@@ -0,0 +1,191 @@
+/*
+ * ====================================================================
+ * 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.validator;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.MessageHeaders;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.CharArrayBuffer;
+import org.apache.hc.core5.util.LangUtils;
+import org.apache.hc.core5.util.Tokenizer;
+
+/**
+ * Represents ETag value.
+ *
+ * @since 5.4
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public final class ETag {
+
+ private final String value;
+ private final ValidatorType type;
+
+ public ETag(final String value, final ValidatorType type) {
+ this.value = Args.notNull(value, "Value");
+ this.type = Args.notNull(type, "Validator type");
+ }
+
+ public ETag(final String value) {
+ this(value, ValidatorType.STRONG);
+ }
+
+ public ValidatorType getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static boolean strongCompare(final ETag t1, final ETag t2) {
+ if (t1 == null || t2 == null) {
+ return false;
+ }
+ return t1.type == ValidatorType.STRONG && t1.type == t2.type && t1.value.equals(t2.value);
+ }
+
+ public static boolean weakCompare(final ETag t1, final ETag t2) {
+ if (t1 == null || t2 == null) {
+ return false;
+ }
+ return t1.value.equals(t2.value);
+ }
+
+ static ETag parse(final CharSequence buf, final Tokenizer.Cursor cursor) {
+ final Tokenizer tokenizer = Tokenizer.INSTANCE;
+ tokenizer.skipWhiteSpace(buf, cursor);
+
+ ValidatorType type = ValidatorType.STRONG;
+
+ if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == 'W') {
+ cursor.updatePos(cursor.getPos() + 1);
+ if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == '/') {
+ type = ValidatorType.WEAK;
+ cursor.updatePos(cursor.getPos() + 1);
+ } else {
+ return null;
+ }
+ }
+
+ String value = null;
+ if (!cursor.atEnd() && buf.charAt(cursor.getPos()) == '"') {
+ cursor.updatePos(cursor.getPos() + 1);
+ final StringBuilder sb = new StringBuilder();
+ for (;;) {
+ if (cursor.atEnd()) {
+ return null;
+ }
+ final char ch = buf.charAt(cursor.getPos());
+ cursor.updatePos(cursor.getPos() + 1);
+ if (ch == '"') {
+ break;
+ } else {
+ sb.append(ch);
+ }
+ }
+ value = sb.toString();
+ }
+
+ tokenizer.skipWhiteSpace(buf, cursor);
+ if (!cursor.atEnd()) {
+ return null;
+ }
+
+ return new ETag(value, type);
+ }
+
+ public static ETag parse(final String s) {
+ if (s == null) {
+ return null;
+ }
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
+ return parse(s, cursor);
+ }
+
+ public static ETag parse(final Header h) {
+ if (h == null) {
+ return null;
+ }
+ final String value = h.getValue();
+ if (value == null) {
+ return null;
+ }
+ return parse(value);
+ }
+
+ public static ETag get(final MessageHeaders message) {
+ if (message == null) {
+ return null;
+ }
+ return parse(message.getFirstHeader(HttpHeaders.ETAG));
+ }
+
+ @Internal
+ public void format(final CharArrayBuffer buf) {
+ if (buf == null) {
+ return;
+ }
+ if (type == ValidatorType.WEAK) {
+ buf.append("W/");
+ }
+ buf.append('"');
+ buf.append(value);
+ buf.append('"');
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof ETag) {
+ final ETag that = (ETag) o;
+ return this.type == that.type && this.value.equals(that.value);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = LangUtils.HASH_SEED;
+ hash = LangUtils.hashCode(hash, type);
+ hash = LangUtils.hashCode(hash, value);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayBuffer buf = new CharArrayBuffer(64);
+ format(buf);
+ return buf.toString();
+ }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java
new file mode 100644
index 000000000..eb13960fc
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/validator/ValidatorType.java
@@ -0,0 +1,37 @@
+/*
+ * ====================================================================
+ * 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.validator;
+
+/**
+ * @since 5.4
+ */
+public enum ValidatorType {
+
+ STRONG, WEAK
+
+}
\ No newline at end of file
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java b/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java
new file mode 100644
index 000000000..865683094
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/validator/TestETag.java
@@ -0,0 +1,93 @@
+/*
+ * ====================================================================
+ * 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.validator;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestETag {
+
+ @Test
+ public void testHashCodeEquals() {
+ final ETag tag1 = new ETag("this");
+ final ETag tag2 = new ETag("this");
+ final ETag tag3 = new ETag("this", ValidatorType.WEAK);
+ final ETag tag4 = new ETag("that", ValidatorType.WEAK);
+
+ Assertions.assertEquals(tag1.hashCode(), tag2.hashCode());
+ Assertions.assertNotEquals(tag2.hashCode(), tag3.hashCode());
+ Assertions.assertNotEquals(tag3.hashCode(), tag4.hashCode());
+
+ Assertions.assertEquals(tag1, tag2);
+ Assertions.assertNotEquals(tag2, tag3);
+ Assertions.assertNotEquals(tag3, tag4);
+ }
+
+ @Test
+ public void testToString() {
+ Assertions.assertEquals("\"blah\"", new ETag("blah").toString());
+ Assertions.assertEquals("W/\"blah\"", new ETag("blah", ValidatorType.WEAK).toString());
+ Assertions.assertEquals("\"\"", new ETag("").toString());
+ }
+
+ @Test
+ public void testParse() {
+ Assertions.assertEquals(new ETag("blah", ValidatorType.WEAK), ETag.parse(" W/\"blah\" "));
+ Assertions.assertEquals(new ETag(" huh?"), ETag.parse(" \" huh?\" "));
+ Assertions.assertEquals(new ETag(" ", ValidatorType.WEAK), ETag.parse("W/\" \""));
+ Assertions.assertEquals(new ETag(""), ETag.parse("\"\""));
+ Assertions.assertNull(ETag.parse("wrong"));
+ Assertions.assertNull(ETag.parse("w/\"wrong\""));
+ Assertions.assertNull(ETag.parse("W /\"wrong\""));
+ Assertions.assertNull(ETag.parse("W/ \"wrong\""));
+ Assertions.assertNull(ETag.parse("W/wrong"));
+ Assertions.assertNull(ETag.parse("\"cut"));
+ Assertions.assertNull(ETag.parse(" \""));
+ Assertions.assertNull(ETag.parse("W/\""));
+ }
+
+ @Test
+ public void testComparison() {
+ Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("1", ValidatorType.WEAK)));
+ Assertions.assertTrue(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("1", ValidatorType.WEAK)));
+ Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("2", ValidatorType.WEAK)));
+ Assertions.assertFalse(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("2", ValidatorType.WEAK)));
+ Assertions.assertFalse(ETag.strongCompare(new ETag("1", ValidatorType.WEAK), new ETag("1")));
+ Assertions.assertTrue(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), new ETag("1")));
+ Assertions.assertTrue(ETag.strongCompare(new ETag("1"), new ETag("1")));
+ Assertions.assertTrue(ETag.weakCompare(new ETag("1"), new ETag("1")));
+
+ Assertions.assertFalse(ETag.weakCompare(new ETag("1", ValidatorType.WEAK), null));
+ Assertions.assertFalse(ETag.weakCompare(null, new ETag("1", ValidatorType.WEAK)));
+ Assertions.assertFalse(ETag.weakCompare(null, null));
+
+ Assertions.assertFalse(ETag.strongCompare(new ETag("1"), null));
+ Assertions.assertFalse(ETag.strongCompare(null, new ETag("1")));
+ Assertions.assertFalse(ETag.strongCompare(null, null));
+ }
+
+}