Better ETag handling
This commit is contained in:
parent
07586902ec
commit
a1e8e9082e
|
@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.Contract;
|
||||||
import org.apache.hc.core5.annotation.Internal;
|
import org.apache.hc.core5.annotation.Internal;
|
||||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
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> expiresRef;
|
||||||
private final AtomicReference<Instant> lastModifiedRef;
|
private final AtomicReference<Instant> lastModifiedRef;
|
||||||
|
|
||||||
|
private final AtomicReference<ETag> eTagRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal constructor that makes no validation of the input parameters and makes
|
* Internal constructor that makes no validation of the input parameters and makes
|
||||||
* no copies of the original client request and the origin response.
|
* 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.dateRef = new AtomicReference<>();
|
||||||
this.expiresRef = new AtomicReference<>();
|
this.expiresRef = new AtomicReference<>();
|
||||||
this.lastModifiedRef = 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.dateRef = new AtomicReference<>();
|
||||||
this.expiresRef = new AtomicReference<>();
|
this.expiresRef = new AtomicReference<>();
|
||||||
this.lastModifiedRef = 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);
|
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.
|
* Returns the {@link Resource} containing the origin response body.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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.impl.ExecSupport;
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||||
import org.apache.hc.client5.http.schedule.SchedulingStrategy;
|
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.Contract;
|
||||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||||
import org.apache.hc.core5.concurrent.CancellableDependency;
|
import org.apache.hc.core5.concurrent.CancellableDependency;
|
||||||
|
@ -1206,16 +1206,17 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
final Collection<CacheHit> variants) {
|
final Collection<CacheHit> variants) {
|
||||||
final String exchangeId = scope.exchangeId;
|
final String exchangeId = scope.exchangeId;
|
||||||
final CancellableDependency operation = scope.cancellableDependency;
|
final CancellableDependency operation = scope.cancellableDependency;
|
||||||
final Map<String, CacheHit> variantMap = new HashMap<>();
|
final Map<ETag, CacheHit> variantMap = new HashMap<>();
|
||||||
for (final CacheHit variant : variants) {
|
for (final CacheHit variant : variants) {
|
||||||
final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
|
final ETag eTag = variant.entry.getETag();
|
||||||
if (header != null) {
|
if (eTag != null) {
|
||||||
variantMap.put(header.getValue(), variant);
|
variantMap.put(eTag, variant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
|
final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
|
||||||
BasicRequestBuilder.copy(request).build(),
|
request,
|
||||||
new ArrayList<>(variantMap.keySet()));
|
variantMap.keySet());
|
||||||
|
|
||||||
final Instant requestDate = getCurrentDate();
|
final Instant requestDate = getCurrentDate();
|
||||||
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
|
chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
|
||||||
|
@ -1270,14 +1271,13 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
||||||
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
|
if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
|
||||||
callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
|
callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
|
||||||
} else {
|
} else {
|
||||||
final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
|
final ETag resultEtag = ETag.get(backendResponse);
|
||||||
if (resultEtagHeader == null) {
|
if (resultEtag == null) {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("{} 304 response did not contain ETag", exchangeId);
|
LOG.debug("{} 304 response did not contain ETag", exchangeId);
|
||||||
}
|
}
|
||||||
callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
|
callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
|
||||||
} else {
|
} else {
|
||||||
final String resultEtag = resultEtagHeader.getValue();
|
|
||||||
final CacheHit match = variantMap.get(resultEtag);
|
final CacheHit match = variantMap.get(resultEtag);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
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.ResourceFactory;
|
||||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||||
import org.apache.hc.client5.http.impl.Operations;
|
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.CallbackContribution;
|
||||||
import org.apache.hc.core5.concurrent.Cancellable;
|
import org.apache.hc.core5.concurrent.Cancellable;
|
||||||
import org.apache.hc.core5.concurrent.ComplexCancellable;
|
import org.apache.hc.core5.concurrent.ComplexCancellable;
|
||||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
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.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpHost;
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
|
@ -520,10 +519,10 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Evicting root cache entry {}", rootKey);
|
LOG.debug("Evicting root cache entry {}", rootKey);
|
||||||
}
|
}
|
||||||
final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
|
final ETag existingETag = root.getETag();
|
||||||
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
|
final ETag newETag = ETag.get(response);
|
||||||
if (existingETag != null && newETag != null &&
|
if (existingETag != null && newETag != null &&
|
||||||
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
|
!ETag.strongCompare(existingETag, newETag) &&
|
||||||
!HttpCacheEntry.isNewer(root, response)) {
|
!HttpCacheEntry.isNewer(root, response)) {
|
||||||
evictAll(root, rootKey);
|
evictAll(root, rootKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
|
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.Resource;
|
||||||
import org.apache.hc.client5.http.cache.ResourceFactory;
|
import org.apache.hc.client5.http.cache.ResourceFactory;
|
||||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
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.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpHost;
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
|
@ -329,10 +328,10 @@ class BasicHttpCache implements HttpCache {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Header existingETag = root.getFirstHeader(HttpHeaders.ETAG);
|
final ETag existingETag = root.getETag();
|
||||||
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
|
final ETag newETag = ETag.get(response);
|
||||||
if (existingETag != null && newETag != null &&
|
if (existingETag != null && newETag != null &&
|
||||||
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
|
!ETag.strongCompare(existingETag, newETag) &&
|
||||||
!HttpCacheEntry.isNewer(root, response)) {
|
!HttpCacheEntry.isNewer(root, response)) {
|
||||||
evictAll(root, rootKey);
|
evictAll(root, rootKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Resource;
|
||||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.ContentType;
|
||||||
import org.apache.hc.core5.http.Header;
|
import org.apache.hc.core5.http.Header;
|
||||||
import org.apache.hc.core5.http.HttpHeaders;
|
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
|
// - ETag and/or Content-Location, if the header would have been sent
|
||||||
// in a 200 response to the same request
|
// in a 200 response to the same request
|
||||||
final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
|
final ETag eTag = entry.getETag();
|
||||||
if (etagHeader != null) {
|
if (eTag != null) {
|
||||||
response.addHeader(etagHeader);
|
response.addHeader(new BasicHeader(HttpHeaders.ETAG, eTag.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Header contentLocationHeader = entry.getFirstHeader(HttpHeaders.CONTENT_LOCATION);
|
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
|
//above listed fields unless said metadata exists for the purpose of
|
||||||
//guiding cache updates (e.g., Last-Modified might be useful if the
|
//guiding cache updates (e.g., Last-Modified might be useful if the
|
||||||
//response does not have an ETag field).
|
//response does not have an ETag field).
|
||||||
if (etagHeader == null) {
|
if (eTag == null) {
|
||||||
final Header lastModifiedHeader = entry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
final Header lastModifiedHeader = entry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
||||||
if (lastModifiedHeader != null) {
|
if (lastModifiedHeader != null) {
|
||||||
response.addHeader(lastModifiedHeader);
|
response.addHeader(lastModifiedHeader);
|
||||||
|
|
|
@ -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.RequestCacheControl;
|
||||||
import org.apache.hc.client5.http.cache.ResponseCacheControl;
|
import org.apache.hc.client5.http.cache.ResponseCacheControl;
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.Header;
|
||||||
import org.apache.hc.core5.http.HeaderElement;
|
|
||||||
import org.apache.hc.core5.http.HttpHeaders;
|
import org.apache.hc.core5.http.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
import org.apache.hc.core5.http.HttpStatus;
|
import org.apache.hc.core5.http.HttpStatus;
|
||||||
|
@ -332,13 +332,14 @@ class CachedResponseSuitabilityChecker {
|
||||||
* @return boolean does the etag validator match
|
* @return boolean does the etag validator match
|
||||||
*/
|
*/
|
||||||
boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) {
|
boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) {
|
||||||
final Header etagHeader = entry.getFirstHeader(HttpHeaders.ETAG);
|
final ETag etag = entry.getETag();
|
||||||
final String etag = (etagHeader != null) ? etagHeader.getValue() : null;
|
if (etag == null) {
|
||||||
final Iterator<HeaderElement> it = MessageSupport.iterate(request, HttpHeaders.IF_NONE_MATCH);
|
return false;
|
||||||
|
}
|
||||||
|
final Iterator<String> it = MessageSupport.iterateTokens(request, HttpHeaders.IF_NONE_MATCH);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
final HeaderElement elt = it.next();
|
final String token = it.next();
|
||||||
final String reqEtag = elt.toString();
|
if ("*".equals(token) || ETag.weakCompare(etag, ETag.parse(token))) {
|
||||||
if (("*".equals(reqEtag) && etag != null) || reqEtag.equals(etag)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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.classic.ExecChainHandler;
|
||||||
import org.apache.hc.client5.http.impl.ExecSupport;
|
import org.apache.hc.client5.http.impl.ExecSupport;
|
||||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
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.ClassicHttpRequest;
|
||||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
import org.apache.hc.core5.http.ContentType;
|
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 List<CacheHit> variants) throws IOException, HttpException {
|
||||||
final String exchangeId = scope.exchangeId;
|
final String exchangeId = scope.exchangeId;
|
||||||
|
|
||||||
final Map<String, CacheHit> variantMap = new HashMap<>();
|
final Map<ETag, CacheHit> variantMap = new HashMap<>();
|
||||||
for (final CacheHit variant : variants) {
|
for (final CacheHit variant : variants) {
|
||||||
final Header header = variant.entry.getFirstHeader(HttpHeaders.ETAG);
|
final ETag eTag = variant.entry.getETag();
|
||||||
if (header != null) {
|
if (eTag != null) {
|
||||||
variantMap.put(header.getValue(), variant);
|
variantMap.put(eTag, variant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
|
final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
|
||||||
request,
|
request,
|
||||||
new ArrayList<>(variantMap.keySet()));
|
variantMap.keySet());
|
||||||
|
|
||||||
final Instant requestDate = getCurrentDate();
|
final Instant requestDate = getCurrentDate();
|
||||||
final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
|
final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
|
||||||
|
@ -629,15 +629,14 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
||||||
backendResponse.close();
|
backendResponse.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Header resultEtagHeader = backendResponse.getFirstHeader(HttpHeaders.ETAG);
|
final ETag resultEtag = ETag.get(backendResponse);
|
||||||
if (resultEtagHeader == null) {
|
if (resultEtag == null) {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("{} 304 response did not contain ETag", exchangeId);
|
LOG.debug("{} 304 response did not contain ETag", exchangeId);
|
||||||
}
|
}
|
||||||
return callBackend(target, request, scope, chain);
|
return callBackend(target, request, scope, chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String resultEtag = resultEtagHeader.getValue();
|
|
||||||
final CacheHit match = variantMap.get(resultEtag);
|
final CacheHit match = variantMap.get(resultEtag);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
|
|
|
@ -26,16 +26,18 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hc.client5.http.impl.cache;
|
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.HeaderConstants;
|
||||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||||
import org.apache.hc.client5.http.cache.ResponseCacheControl;
|
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.function.Factory;
|
||||||
import org.apache.hc.core5.http.Header;
|
import org.apache.hc.core5.http.Header;
|
||||||
import org.apache.hc.core5.http.HttpHeaders;
|
import org.apache.hc.core5.http.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
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> {
|
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) {
|
public T buildConditionalRequest(final ResponseCacheControl cacheControl, final T request, final HttpCacheEntry cacheEntry) {
|
||||||
final T newRequest = messageCopier.create(request);
|
final T newRequest = messageCopier.create(request);
|
||||||
|
|
||||||
final Header eTag = cacheEntry.getFirstHeader(HttpHeaders.ETAG);
|
final ETag eTag = cacheEntry.getETag();
|
||||||
if (eTag != null) {
|
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);
|
final Header lastModified = cacheEntry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
|
||||||
if (lastModified != null) {
|
if (lastModified != null) {
|
||||||
|
@ -85,9 +87,20 @@ class ConditionalRequestBuilder<T extends HttpRequest> {
|
||||||
* @param variants
|
* @param variants
|
||||||
* @return the wrapped request
|
* @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);
|
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;
|
return newRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.HttpGet;
|
||||||
import org.apache.hc.client5.http.classic.methods.HttpOptions;
|
import org.apache.hc.client5.http.classic.methods.HttpOptions;
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.ClassicHttpRequest;
|
||||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
import org.apache.hc.core5.http.Header;
|
import org.apache.hc.core5.http.Header;
|
||||||
|
@ -997,20 +998,20 @@ public class TestCachingExecChain {
|
||||||
final Instant now = Instant.now();
|
final Instant now = Instant.now();
|
||||||
|
|
||||||
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
|
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/");
|
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();
|
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
|
||||||
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
|
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
resp1.setHeader("Cache-Control", "max-age=1");
|
resp1.setHeader("Cache-Control", "max-age=1");
|
||||||
resp1.setHeader("Etag", "etag");
|
resp1.setHeader("Etag", "\"etag\"");
|
||||||
|
|
||||||
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
|
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
|
||||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
resp2.setHeader("Cache-Control", "max-age=1");
|
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);
|
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||||
|
|
||||||
|
@ -1020,9 +1021,9 @@ public class TestCachingExecChain {
|
||||||
final ClassicHttpResponse result2 = execute(req2);
|
final ClassicHttpResponse result2 = execute(req2);
|
||||||
|
|
||||||
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
|
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(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
|
||||||
Assertions.assertEquals("etag", result2.getFirstHeader("Etag").getValue());
|
Assertions.assertEquals(new ETag("etag"), ETag.get(result2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1031,21 +1032,21 @@ public class TestCachingExecChain {
|
||||||
final Instant now = Instant.now();
|
final Instant now = Instant.now();
|
||||||
|
|
||||||
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
|
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/");
|
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();
|
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
|
||||||
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
|
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
resp1.setHeader("Cache-Control", "max-age=1");
|
resp1.setHeader("Cache-Control", "max-age=1");
|
||||||
resp1.setHeader("Etag", "etag");
|
resp1.setHeader("Etag", "\"etag\"");
|
||||||
resp1.setHeader("Vary", "Accept-Encoding");
|
resp1.setHeader("Vary", "Accept-Encoding");
|
||||||
|
|
||||||
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
|
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
|
||||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||||
resp2.setHeader("Cache-Control", "max-age=1");
|
resp2.setHeader("Cache-Control", "max-age=1");
|
||||||
resp1.setHeader("Etag", "etag");
|
resp1.setHeader("Etag", "\"etag\"");
|
||||||
resp1.setHeader("Vary", "Accept-Encoding");
|
resp1.setHeader("Vary", "Accept-Encoding");
|
||||||
|
|
||||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||||
|
@ -1057,9 +1058,9 @@ public class TestCachingExecChain {
|
||||||
final ClassicHttpResponse result2 = execute(req2);
|
final ClassicHttpResponse result2 = execute(req2);
|
||||||
|
|
||||||
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result1.getCode());
|
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(HttpStatus.SC_NOT_MODIFIED, result2.getCode());
|
||||||
Assertions.assertEquals("etag", result2.getFirstHeader("Etag").getValue());
|
Assertions.assertEquals(new ETag("etag"), ETag.get(result2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -27,19 +27,26 @@
|
||||||
package org.apache.hc.client5.http.impl.cache;
|
package org.apache.hc.client5.http.impl.cache;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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.HttpCacheEntry;
|
||||||
import org.apache.hc.client5.http.cache.RequestCacheControl;
|
import org.apache.hc.client5.http.cache.RequestCacheControl;
|
||||||
import org.apache.hc.client5.http.cache.ResponseCacheControl;
|
import org.apache.hc.client5.http.cache.ResponseCacheControl;
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.Header;
|
||||||
import org.apache.hc.core5.http.HttpHeaders;
|
import org.apache.hc.core5.http.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpRequest;
|
import org.apache.hc.core5.http.HttpRequest;
|
||||||
import org.apache.hc.core5.http.message.BasicHeader;
|
import org.apache.hc.core5.http.message.BasicHeader;
|
||||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
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.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.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -76,11 +83,11 @@ public class TestConditionalRequestBuilder {
|
||||||
Assertions.assertEquals(theUri, newRequest.getRequestUri());
|
Assertions.assertEquals(theUri, newRequest.getRequestUri());
|
||||||
Assertions.assertEquals(2, newRequest.getHeaders().length);
|
Assertions.assertEquals(2, newRequest.getHeaders().length);
|
||||||
|
|
||||||
Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
|
MatcherAssert.assertThat(
|
||||||
Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
|
newRequest.getHeaders(),
|
||||||
|
HeadersMatcher.same(
|
||||||
Assertions.assertEquals("If-Modified-Since", newRequest.getHeaders()[1].getName());
|
new BasicHeader("Accept-Encoding", "gzip"),
|
||||||
Assertions.assertEquals(lastModified, newRequest.getHeaders()[1].getValue());
|
new BasicHeader("If-Modified-Since", lastModified)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -110,14 +117,16 @@ public class TestConditionalRequestBuilder {
|
||||||
public void testBuildConditionalRequestWithETag() {
|
public void testBuildConditionalRequestWithETag() {
|
||||||
final String theMethod = "GET";
|
final String theMethod = "GET";
|
||||||
final String theUri = "/theuri";
|
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);
|
final HttpRequest basicRequest = new BasicHttpRequest(theMethod, theUri);
|
||||||
basicRequest.addHeader("Accept-Encoding", "gzip");
|
basicRequest.addHeader("Accept-Encoding", "gzip");
|
||||||
|
|
||||||
|
final Instant now = Instant.now();
|
||||||
|
|
||||||
final Header[] headers = new Header[] {
|
final Header[] headers = new Header[] {
|
||||||
new BasicHeader("Date", DateUtils.formatStandardDate(Instant.now())),
|
new BasicHeader("Date", DateUtils.formatStandardDate(now)),
|
||||||
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(Instant.now())),
|
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(now)),
|
||||||
new BasicHeader("ETag", theETag) };
|
new BasicHeader("ETag", theETag) };
|
||||||
|
|
||||||
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
|
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry(headers);
|
||||||
|
@ -128,13 +137,12 @@ public class TestConditionalRequestBuilder {
|
||||||
Assertions.assertEquals(theMethod, newRequest.getMethod());
|
Assertions.assertEquals(theMethod, newRequest.getMethod());
|
||||||
Assertions.assertEquals(theUri, newRequest.getRequestUri());
|
Assertions.assertEquals(theUri, newRequest.getRequestUri());
|
||||||
|
|
||||||
Assertions.assertEquals(3, newRequest.getHeaders().length);
|
MatcherAssert.assertThat(
|
||||||
|
newRequest.getHeaders(),
|
||||||
Assertions.assertEquals("Accept-Encoding", newRequest.getHeaders()[0].getName());
|
HeadersMatcher.same(
|
||||||
Assertions.assertEquals("gzip", newRequest.getHeaders()[0].getValue());
|
new BasicHeader("Accept-Encoding", "gzip"),
|
||||||
|
new BasicHeader("If-None-Match", theETag),
|
||||||
Assertions.assertEquals("If-None-Match", newRequest.getHeaders()[1].getName());
|
new BasicHeader("If-Modified-Since", DateUtils.formatStandardDate(now))));
|
||||||
Assertions.assertEquals(theETag, newRequest.getHeaders()[1].getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -260,25 +268,21 @@ public class TestConditionalRequestBuilder {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildConditionalRequestFromVariants() throws Exception {
|
public void testBuildConditionalRequestFromVariants() throws Exception {
|
||||||
final String etag1 = "\"123\"";
|
final ETag etag1 = new ETag("123");
|
||||||
final String etag2 = "\"456\"";
|
final ETag etag2 = new ETag("456");
|
||||||
final String etag3 = "\"789\"";
|
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);
|
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();
|
final Iterator<String> it = MessageSupport.iterateTokens(conditional, HttpHeaders.IF_NONE_MATCH);
|
||||||
Assertions.assertTrue(ifNoneMatch.contains(etag1));
|
final List<ETag> etags = new ArrayList<>();
|
||||||
Assertions.assertTrue(ifNoneMatch.contains(etag2));
|
while (it.hasNext()) {
|
||||||
Assertions.assertTrue(ifNoneMatch.contains(etag3));
|
etags.add(ETag.parse(it.next()));
|
||||||
ifNoneMatch = ifNoneMatch.replace(etag1, "");
|
}
|
||||||
ifNoneMatch = ifNoneMatch.replace(etag2, "");
|
MatcherAssert.assertThat(etags, Matchers.containsInAnyOrder(etag1, etag2, etag3));
|
||||||
ifNoneMatch = ifNoneMatch.replace(etag3, "");
|
|
||||||
ifNoneMatch = ifNoneMatch.replace(",","");
|
|
||||||
ifNoneMatch = ifNoneMatch.replace(" ", "");
|
|
||||||
Assertions.assertEquals(ifNoneMatch, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,8 @@ import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import org.apache.hc.client5.http.utils.DateUtils;
|
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.Contract;
|
||||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||||
import org.apache.hc.core5.http.EntityDetails;
|
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.");
|
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 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.");
|
throw new ProtocolException("'If-Range' header must not contain a weak entity tag.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.hc.core5.annotation.Contract;
|
||||||
import org.apache.hc.core5.annotation.Internal;
|
import org.apache.hc.core5.annotation.Internal;
|
||||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||||
import org.apache.hc.core5.http.Header;
|
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.http.MessageHeaders;
|
||||||
import org.apache.hc.core5.util.Args;
|
import org.apache.hc.core5.util.Args;
|
||||||
import org.apache.hc.core5.util.CharArrayBuffer;
|
import org.apache.hc.core5.util.CharArrayBuffer;
|
||||||
|
|
Loading…
Reference in New Issue