ok2c commented on code in PR #881:
URL:
https://github.com/apache/httpcomponents-client/pull/881#discussion_r3874129644
##########
httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java:
##########
@@ -188,6 +313,233 @@ public void failed(final Exception ex) {
});
}
+ private static List<String> createDictionaryAcceptTokens(
+ final CompressionDictionaryStore compressionDictionaryStore) {
+ final List<String> tokens = new ArrayList<>();
+ if (compressionDictionaryStore != null && Brotli4jRuntime.available())
{
+ tokens.add(ContentCoding.DCB.token());
+ }
+ if (compressionDictionaryStore != null && ZstdRuntime.available()) {
+ tokens.add(ContentCoding.DCZ.token());
+ }
+ return tokens;
+ }
+
+ private List<String> selectDictionaryAcceptTokens(
+ final CompressionDictionary dictionary) {
+ if (dictionary == null
+ || !dictionaryAcceptTokens.contains(ContentCoding.DCZ.token())
+ || isDczDictionaryCompatible(dictionary)) {
+ return dictionaryAcceptTokens;
+ }
+ final List<String> tokens = new ArrayList<>(dictionaryAcceptTokens);
+ tokens.remove(ContentCoding.DCZ.token());
+ return tokens;
+ }
+
+ private static boolean isDczDictionaryCompatible(
+ final CompressionDictionary dictionary) {
+ return dictionary.getContentLength() >= 8
+ && !dictionary.contentStartsWith(
+ (byte) 0x37, (byte) 0xa4, (byte) 0x30, (byte) 0xec);
+ }
+
+ private CompressionDictionary prepareDictionaryNegotiation(
+ final HttpRequest request,
+ final boolean enabled,
+ final CompressionDictionary candidate,
+ final List<String> requestDictionaryAcceptTokens) throws
HttpException {
+ if (!enabled) {
+ return null;
+ }
+
+ if (request.containsHeader(HttpHeaders.ACCEPT_ENCODING)) {
+ final boolean dcbRequested = containsContentCoding(
+ request, ContentCoding.DCB.token());
+ final boolean dczRequested = containsContentCoding(
+ request, ContentCoding.DCZ.token());
+
+ if (!dcbRequested && !dczRequested) {
+ return null;
+ }
+ if (candidate == null) {
+ throw new HttpException(
+ "Dictionary-aware Accept-Encoding without a matching
dictionary");
+ }
+ if (dcbRequested
+ &&
!requestDictionaryAcceptTokens.contains(ContentCoding.DCB.token())) {
+ throw new HttpException(
+ "Unsupported Accept-Encoding: " +
ContentCoding.DCB.token());
+ }
+ if (dczRequested
+ &&
!requestDictionaryAcceptTokens.contains(ContentCoding.DCZ.token())) {
+ throw new HttpException(
+ "Unsupported Accept-Encoding: " +
ContentCoding.DCZ.token());
+ }
+
+ addDictionaryHeaders(request, candidate);
+ return candidate;
+ }
+
+ if (candidate == null || requestDictionaryAcceptTokens.isEmpty()) {
+ request.addHeader(MessageSupport.headerOfTokens(
+ HttpHeaders.ACCEPT_ENCODING, acceptTokens));
+ return null;
+ }
+
+ final List<String> tokens = new ArrayList<>(
+ acceptTokens.size() + requestDictionaryAcceptTokens.size());
+ tokens.addAll(acceptTokens);
+ tokens.addAll(requestDictionaryAcceptTokens);
+ request.addHeader(MessageSupport.headerOfTokens(
+ HttpHeaders.ACCEPT_ENCODING, tokens));
+
+ addDictionaryHeaders(request, candidate);
+ return candidate;
+ }
+
+ private static void addDictionaryHeaders(
+ final HttpRequest request,
+ final CompressionDictionary dictionary) {
+ request.addHeader(
+ CompressionDictionaryHeaderSupport.AVAILABLE_DICTIONARY,
+ CompressionDictionaryHeaderSupport.formatAvailableDictionary(
+ dictionary.getSha256()));
+ if (!dictionary.getId().isEmpty()) {
+ request.addHeader(
+ CompressionDictionaryHeaderSupport.DICTIONARY_ID,
+ CompressionDictionaryHeaderSupport.formatDictionaryId(
+ dictionary.getId()));
+ }
+ }
+
+ private static boolean containsContentCoding(
+ final HttpRequest request,
+ final String expected) {
+ final Header[] headers =
request.getHeaders(HttpHeaders.ACCEPT_ENCODING);
+ for (final Header header : headers) {
+ final String value = header.getValue();
+ final ParserCursor cursor = new ParserCursor(0, value.length());
+ final HeaderElement[] elements =
BasicHeaderValueParser.INSTANCE.parseElements(
+ value, cursor);
+ for (final HeaderElement element : elements) {
+ if (expected.equalsIgnoreCase(element.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private CompressionDictionary findDictionary(
+ final HttpRequest request,
+ final URI requestUri,
+ final CookieStore privacyPartition) {
+ if (compressionDictionaryStore == null
+ || compressionDictionaryMatcher == null
+ || privacyPartition == null
+ || requestUri == null
+ || !URIScheme.HTTPS.same(requestUri.getScheme())
+ ||
request.containsHeader(CompressionDictionaryHeaderSupport.AVAILABLE_DICTIONARY)
+ ||
request.containsHeader(CompressionDictionaryHeaderSupport.DICTIONARY_ID)) {
+ return null;
+ }
+
+ return compressionDictionaryMatcher.match(
+ requestUri,
+ null,
+ compressionDictionaryStore.getByOrigin(privacyPartition,
requestUri));
+ }
+
+ private UseAsDictionary parseUseAsDictionary(
+ final HttpResponse response,
+ final URI requestUri,
+ final CookieStore privacyPartition) {
+ if (compressionDictionaryStore == null
+ || privacyPartition == null
+ || requestUri == null
+ || !URIScheme.HTTPS.same(requestUri.getScheme())) {
+ return null;
+ }
+
+ final Header[] headers =
response.getHeaders(CompressionDictionaryHeaderSupport.USE_AS_DICTIONARY);
+ if (headers == null || headers.length == 0) {
+ return null;
+ }
+
+ final StringBuilder value = new StringBuilder();
+ for (final Header header : headers) {
+ if (value.length() > 0) {
+ value.append(',');
+ }
+ value.append(header.getValue());
+ }
+
+ try {
+ final UseAsDictionary useAsDictionary =
UseAsDictionary.parse(value.toString());
+ if (!useAsDictionary.isSupported()
+ || !new
DefaultCompressionDictionaryUrlPatternMatcher().isValid(
+ useAsDictionary.getMatch(), requestUri)) {
+ return null;
+ }
+ return useAsDictionary;
+ } catch (final ParseException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ private CookieStore getPrivacyPartition(final HttpClientContext context)
throws HttpException {
+ final CookieStore cookieStore = context.getCookieStore();
+ if (cookieStore instanceof CompressionDictionaryCookieStore
+ && ((CompressionDictionaryCookieStore) cookieStore)
+ .isBoundTo(compressionDictionaryStore)) {
+ return cookieStore;
+ }
+ if (compressionDictionaryStore != null) {
+ throw new HttpException(
+ "Compression Dictionary Transport requires its managed
cookie store");
+ }
+ return null;
+ }
+
+ private static boolean containsToken(
+ final Map<String, ?> map,
+ final String expected) {
+ for (final String token : map.keySet()) {
+ if (expected.equalsIgnoreCase(token)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static URI resolveRequestUri(
+ final HttpRequest request,
+ final AsyncExecChain.Scope scope) {
+ try {
+ if (scope != null) {
Review Comment:
@arturobernalg The scope can never be null. Please simplify.
##########
httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java:
##########
@@ -188,6 +313,233 @@ public void failed(final Exception ex) {
});
}
+ private static List<String> createDictionaryAcceptTokens(
+ final CompressionDictionaryStore compressionDictionaryStore) {
+ final List<String> tokens = new ArrayList<>();
+ if (compressionDictionaryStore != null && Brotli4jRuntime.available())
{
+ tokens.add(ContentCoding.DCB.token());
+ }
+ if (compressionDictionaryStore != null && ZstdRuntime.available()) {
+ tokens.add(ContentCoding.DCZ.token());
+ }
+ return tokens;
+ }
+
+ private List<String> selectDictionaryAcceptTokens(
+ final CompressionDictionary dictionary) {
+ if (dictionary == null
+ || !dictionaryAcceptTokens.contains(ContentCoding.DCZ.token())
+ || isDczDictionaryCompatible(dictionary)) {
+ return dictionaryAcceptTokens;
+ }
+ final List<String> tokens = new ArrayList<>(dictionaryAcceptTokens);
+ tokens.remove(ContentCoding.DCZ.token());
+ return tokens;
+ }
+
+ private static boolean isDczDictionaryCompatible(
+ final CompressionDictionary dictionary) {
+ return dictionary.getContentLength() >= 8
+ && !dictionary.contentStartsWith(
+ (byte) 0x37, (byte) 0xa4, (byte) 0x30, (byte) 0xec);
+ }
+
+ private CompressionDictionary prepareDictionaryNegotiation(
+ final HttpRequest request,
+ final boolean enabled,
+ final CompressionDictionary candidate,
+ final List<String> requestDictionaryAcceptTokens) throws
HttpException {
+ if (!enabled) {
+ return null;
+ }
+
+ if (request.containsHeader(HttpHeaders.ACCEPT_ENCODING)) {
+ final boolean dcbRequested = containsContentCoding(
+ request, ContentCoding.DCB.token());
+ final boolean dczRequested = containsContentCoding(
+ request, ContentCoding.DCZ.token());
+
+ if (!dcbRequested && !dczRequested) {
+ return null;
+ }
+ if (candidate == null) {
+ throw new HttpException(
+ "Dictionary-aware Accept-Encoding without a matching
dictionary");
+ }
+ if (dcbRequested
+ &&
!requestDictionaryAcceptTokens.contains(ContentCoding.DCB.token())) {
+ throw new HttpException(
+ "Unsupported Accept-Encoding: " +
ContentCoding.DCB.token());
+ }
+ if (dczRequested
+ &&
!requestDictionaryAcceptTokens.contains(ContentCoding.DCZ.token())) {
+ throw new HttpException(
+ "Unsupported Accept-Encoding: " +
ContentCoding.DCZ.token());
+ }
+
+ addDictionaryHeaders(request, candidate);
+ return candidate;
+ }
+
+ if (candidate == null || requestDictionaryAcceptTokens.isEmpty()) {
+ request.addHeader(MessageSupport.headerOfTokens(
+ HttpHeaders.ACCEPT_ENCODING, acceptTokens));
+ return null;
+ }
+
+ final List<String> tokens = new ArrayList<>(
+ acceptTokens.size() + requestDictionaryAcceptTokens.size());
+ tokens.addAll(acceptTokens);
+ tokens.addAll(requestDictionaryAcceptTokens);
+ request.addHeader(MessageSupport.headerOfTokens(
+ HttpHeaders.ACCEPT_ENCODING, tokens));
+
+ addDictionaryHeaders(request, candidate);
+ return candidate;
+ }
+
+ private static void addDictionaryHeaders(
+ final HttpRequest request,
+ final CompressionDictionary dictionary) {
+ request.addHeader(
+ CompressionDictionaryHeaderSupport.AVAILABLE_DICTIONARY,
+ CompressionDictionaryHeaderSupport.formatAvailableDictionary(
+ dictionary.getSha256()));
+ if (!dictionary.getId().isEmpty()) {
+ request.addHeader(
+ CompressionDictionaryHeaderSupport.DICTIONARY_ID,
+ CompressionDictionaryHeaderSupport.formatDictionaryId(
+ dictionary.getId()));
+ }
+ }
+
+ private static boolean containsContentCoding(
+ final HttpRequest request,
+ final String expected) {
+ final Header[] headers =
request.getHeaders(HttpHeaders.ACCEPT_ENCODING);
+ for (final Header header : headers) {
+ final String value = header.getValue();
+ final ParserCursor cursor = new ParserCursor(0, value.length());
+ final HeaderElement[] elements =
BasicHeaderValueParser.INSTANCE.parseElements(
+ value, cursor);
+ for (final HeaderElement element : elements) {
+ if (expected.equalsIgnoreCase(element.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private CompressionDictionary findDictionary(
+ final HttpRequest request,
+ final URI requestUri,
+ final CookieStore privacyPartition) {
+ if (compressionDictionaryStore == null
+ || compressionDictionaryMatcher == null
+ || privacyPartition == null
+ || requestUri == null
+ || !URIScheme.HTTPS.same(requestUri.getScheme())
+ ||
request.containsHeader(CompressionDictionaryHeaderSupport.AVAILABLE_DICTIONARY)
+ ||
request.containsHeader(CompressionDictionaryHeaderSupport.DICTIONARY_ID)) {
+ return null;
+ }
+
+ return compressionDictionaryMatcher.match(
+ requestUri,
+ null,
+ compressionDictionaryStore.getByOrigin(privacyPartition,
requestUri));
+ }
+
+ private UseAsDictionary parseUseAsDictionary(
+ final HttpResponse response,
+ final URI requestUri,
+ final CookieStore privacyPartition) {
+ if (compressionDictionaryStore == null
+ || privacyPartition == null
+ || requestUri == null
+ || !URIScheme.HTTPS.same(requestUri.getScheme())) {
+ return null;
+ }
+
+ final Header[] headers =
response.getHeaders(CompressionDictionaryHeaderSupport.USE_AS_DICTIONARY);
+ if (headers == null || headers.length == 0) {
+ return null;
+ }
+
+ final StringBuilder value = new StringBuilder();
+ for (final Header header : headers) {
+ if (value.length() > 0) {
+ value.append(',');
+ }
+ value.append(header.getValue());
+ }
+
+ try {
+ final UseAsDictionary useAsDictionary =
UseAsDictionary.parse(value.toString());
+ if (!useAsDictionary.isSupported()
+ || !new
DefaultCompressionDictionaryUrlPatternMatcher().isValid(
+ useAsDictionary.getMatch(), requestUri)) {
+ return null;
+ }
+ return useAsDictionary;
+ } catch (final ParseException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ private CookieStore getPrivacyPartition(final HttpClientContext context)
throws HttpException {
+ final CookieStore cookieStore = context.getCookieStore();
+ if (cookieStore instanceof CompressionDictionaryCookieStore
+ && ((CompressionDictionaryCookieStore) cookieStore)
+ .isBoundTo(compressionDictionaryStore)) {
+ return cookieStore;
+ }
+ if (compressionDictionaryStore != null) {
+ throw new HttpException(
+ "Compression Dictionary Transport requires its managed
cookie store");
+ }
+ return null;
+ }
+
+ private static boolean containsToken(
+ final Map<String, ?> map,
+ final String expected) {
+ for (final String token : map.keySet()) {
+ if (expected.equalsIgnoreCase(token)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static URI resolveRequestUri(
+ final HttpRequest request,
+ final AsyncExecChain.Scope scope) {
+ try {
+ if (scope != null) {
+ final URI originalUri = scope.originalRequest.getUri();
+ if (originalUri.isAbsolute()) {
+ return originalUri;
+ }
+ }
+
+ final URI requestUri = request.getUri();
+ if (requestUri.isAbsolute()) {
+ return requestUri;
+ }
+
+ if (scope != null) {
+ final URI baseUri =
URI.create(scope.route.getTargetHost().toURI() + "/");
Review Comment:
@arturobernalg This looks wrong. You should be using the request authority
here, not the target host.
##########
httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java:
##########
@@ -188,6 +313,233 @@ public void failed(final Exception ex) {
});
}
+ private static List<String> createDictionaryAcceptTokens(
+ final CompressionDictionaryStore compressionDictionaryStore) {
+ final List<String> tokens = new ArrayList<>();
+ if (compressionDictionaryStore != null && Brotli4jRuntime.available())
{
+ tokens.add(ContentCoding.DCB.token());
+ }
+ if (compressionDictionaryStore != null && ZstdRuntime.available()) {
+ tokens.add(ContentCoding.DCZ.token());
+ }
+ return tokens;
+ }
+
+ private List<String> selectDictionaryAcceptTokens(
+ final CompressionDictionary dictionary) {
+ if (dictionary == null
+ || !dictionaryAcceptTokens.contains(ContentCoding.DCZ.token())
+ || isDczDictionaryCompatible(dictionary)) {
+ return dictionaryAcceptTokens;
+ }
+ final List<String> tokens = new ArrayList<>(dictionaryAcceptTokens);
+ tokens.remove(ContentCoding.DCZ.token());
+ return tokens;
+ }
+
+ private static boolean isDczDictionaryCompatible(
+ final CompressionDictionary dictionary) {
+ return dictionary.getContentLength() >= 8
+ && !dictionary.contentStartsWith(
+ (byte) 0x37, (byte) 0xa4, (byte) 0x30, (byte) 0xec);
+ }
+
+ private CompressionDictionary prepareDictionaryNegotiation(
+ final HttpRequest request,
+ final boolean enabled,
+ final CompressionDictionary candidate,
+ final List<String> requestDictionaryAcceptTokens) throws
HttpException {
+ if (!enabled) {
+ return null;
+ }
+
+ if (request.containsHeader(HttpHeaders.ACCEPT_ENCODING)) {
Review Comment:
@arturobernalg This whole `Accept-Encoding` parsing business looks really
bad and terribly inefficient.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]