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>