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 2022/05/18 13:18:31 UTC
[httpcomponents-client] branch master updated: add br decompression support (#363)
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
The following commit(s) were added to refs/heads/master by this push:
new db47570ef add br decompression support (#363)
db47570ef is described below
commit db47570efefe32054f6d5a45cf0023e9d6927338
Author: 殷成涛 <yi...@163.com>
AuthorDate: Wed May 18 21:18:25 2022 +0800
add br decompression support (#363)
---
httpclient5/pom.xml | 7 ++-
.../http/entity/BrotliDecompressingEntity.java | 56 ++++++++++++++++++++++
.../http/entity/BrotliInputStreamFactory.java | 54 +++++++++++++++++++++
.../http/impl/classic/ContentCompressionExec.java | 37 ++++++++++----
.../apache/hc/client5/http/entity/TestBrotli.java | 52 ++++++++++++++++++++
.../impl/classic/TestContentCompressionExec.java | 16 +++++++
pom.xml | 6 +++
7 files changed, 218 insertions(+), 10 deletions(-)
diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml
index 6261e2f36..77695c9bd 100644
--- a/httpclient5/pom.xml
+++ b/httpclient5/pom.xml
@@ -81,6 +81,11 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.brotli</groupId>
+ <artifactId>dec</artifactId>
+ <optional>true</optional>
+ </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
@@ -179,4 +184,4 @@
</profile>
</profiles>
-</project>
\ No newline at end of file
+</project>
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliDecompressingEntity.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliDecompressingEntity.java
new file mode 100644
index 000000000..9b8c7bb33
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliDecompressingEntity.java
@@ -0,0 +1,56 @@
+/*
+ * ====================================================================
+ * 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.entity;
+
+import org.apache.hc.core5.http.HttpEntity;
+
+/**
+ * {@link org.apache.hc.core5.http.io.entity.HttpEntityWrapper} responsible for
+ * handling br Content Coded responses.
+ *
+ * @see GzipDecompressingEntity
+ * @since 5.2
+ */
+public class BrotliDecompressingEntity extends DecompressingEntity {
+ /**
+ * Creates a new {@link DecompressingEntity}.
+ *
+ * @param entity factory to create decompressing stream.
+ */
+ public BrotliDecompressingEntity(final HttpEntity entity) {
+ super(entity, BrotliInputStreamFactory.getInstance());
+ }
+
+ public static boolean isAvailable() {
+ try {
+ Class.forName("org.brotli.dec.BrotliInputStream");
+ return true;
+ } catch (final ClassNotFoundException | NoClassDefFoundError e) {
+ return false;
+ }
+ }
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java
new file mode 100644
index 000000000..d65684c8b
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/BrotliInputStreamFactory.java
@@ -0,0 +1,54 @@
+/*
+ * ====================================================================
+ * 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.entity;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.brotli.dec.BrotliInputStream;
+
+/**
+ * {@link InputStreamFactory} for handling Brotli Content Coded responses.
+ *
+ * @since 5.2
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public class BrotliInputStreamFactory implements InputStreamFactory {
+
+ private static final BrotliInputStreamFactory INSTANCE = new BrotliInputStreamFactory();
+
+ public static BrotliInputStreamFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public InputStream create(final InputStream inputStream) throws IOException {
+ return new BrotliInputStream(inputStream);
+ }
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
index d9f3ca6a2..722e2eb8a 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
@@ -34,6 +34,8 @@ import java.util.Locale;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.entity.BrotliDecompressingEntity;
+import org.apache.hc.client5.http.entity.BrotliInputStreamFactory;
import org.apache.hc.client5.http.entity.DecompressingEntity;
import org.apache.hc.client5.http.entity.DeflateInputStreamFactory;
import org.apache.hc.client5.http.entity.GZIPInputStreamFactory;
@@ -84,16 +86,32 @@ public final class ContentCompressionExec implements ExecChainHandler {
final List<String> acceptEncoding,
final Lookup<InputStreamFactory> decoderRegistry,
final boolean ignoreUnknown) {
+
+ final boolean brotliSupported = BrotliDecompressingEntity.isAvailable();
+ final String[] encoding;
+ if (brotliSupported) {
+ encoding = new String[] {"gzip", "x-gzip", "deflate", "br"};
+ } else {
+ encoding = new String[] {"gzip", "x-gzip", "deflate"};
+ }
this.acceptEncoding = MessageSupport.format(HttpHeaders.ACCEPT_ENCODING,
- acceptEncoding != null ? acceptEncoding.toArray(
- EMPTY_STRING_ARRAY) : new String[] {"gzip", "x-gzip", "deflate"});
-
- this.decoderRegistry = decoderRegistry != null ? decoderRegistry :
- RegistryBuilder.<InputStreamFactory>create()
- .register("gzip", GZIPInputStreamFactory.getInstance())
- .register("x-gzip", GZIPInputStreamFactory.getInstance())
- .register("deflate", DeflateInputStreamFactory.getInstance())
- .build();
+ acceptEncoding != null ? acceptEncoding.toArray(
+ EMPTY_STRING_ARRAY) : encoding);
+
+ if (decoderRegistry != null) {
+ this.decoderRegistry = decoderRegistry;
+ } else {
+ final RegistryBuilder<InputStreamFactory> builder = RegistryBuilder.<InputStreamFactory>create()
+ .register("gzip", GZIPInputStreamFactory.getInstance())
+ .register("x-gzip", GZIPInputStreamFactory.getInstance())
+ .register("deflate", DeflateInputStreamFactory.getInstance());
+ if (brotliSupported) {
+ builder.register("br", BrotliInputStreamFactory.getInstance());
+ }
+ this.decoderRegistry = builder.build();
+ }
+
+
this.ignoreUnknown = ignoreUnknown;
}
@@ -107,6 +125,7 @@ public final class ContentCompressionExec implements ExecChainHandler {
* <ul>
* <li>gzip - see {@link java.util.zip.GZIPInputStream}</li>
* <li>deflate - see {@link org.apache.hc.client5.http.entity.DeflateInputStream}</li>
+ * <li>brotli - see {@link org.brotli.dec.BrotliInputStream}</li>
* </ul>
*/
public ContentCompressionExec() {
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestBrotli.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestBrotli.java
new file mode 100644
index 000000000..5b48150fa
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/TestBrotli.java
@@ -0,0 +1,52 @@
+/*
+ * ====================================================================
+ * 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.entity;
+
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestBrotli {
+
+ /**
+ * Brotli decompression test implemented by request with specified response encoding br
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testDecompressionWithBrotli() throws Exception {
+
+ final byte[] bytes = new byte[] {33, 44, 0, 4, 116, 101, 115, 116, 32, 98, 114, 111, 116, 108, 105, 10, 3};
+
+ final HttpEntity entity = new BrotliDecompressingEntity(new ByteArrayEntity(bytes, null));
+ Assertions.assertEquals("test brotli\n", EntityUtils.toString(entity));
+ }
+
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
index ea489c02b..51c8b539e 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
@@ -183,6 +183,22 @@ public class TestContentCompressionExec {
Assertions.assertTrue(entity instanceof StringEntity);
}
+ @Test
+ public void testBrotliContentEncoding() throws Exception {
+ final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/");
+ final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
+ final HttpEntity original = EntityBuilder.create().setText("encoded stuff").setContentEncoding("br").build();
+ response.setEntity(original);
+
+ Mockito.when(execChain.proceed(request, scope)).thenReturn(response);
+
+ impl.execute(request, scope, execChain);
+
+ final HttpEntity entity = response.getEntity();
+ Assertions.assertNotNull(entity);
+ Assertions.assertTrue(entity instanceof DecompressingEntity);
+ }
+
@Test
public void testUnknownContentEncoding() throws Exception {
final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/");
diff --git a/pom.xml b/pom.xml
index 487109c7a..3d12b1b94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,7 @@
<httpcore.version>5.2-beta1</httpcore.version>
<log4j.version>2.17.0</log4j.version>
<commons-codec.version>1.15</commons-codec.version>
+ <brotli.version>0.1.2</brotli.version>
<conscrypt.version>2.5.2</conscrypt.version>
<ehcache.version>3.9.6</ehcache.version>
<memcached.version>2.12.3</memcached.version>
@@ -148,6 +149,11 @@
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.brotli</groupId>
+ <artifactId>dec</artifactId>
+ <version>${brotli.version}</version>
+ </dependency>
<dependency>
<groupId>org.conscrypt</groupId>
<artifactId>conscrypt-openjdk-uber</artifactId>