add br decompression support (#363)

This commit is contained in:
殷成涛 2022-05-18 21:18:25 +08:00 committed by GitHub
parent 35732cacb2
commit db47570efe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 218 additions and 10 deletions

View File

@ -81,6 +81,11 @@
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -34,6 +34,8 @@ import java.util.Locale;
import org.apache.hc.client5.http.classic.ExecChain; 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.config.RequestConfig; 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.DecompressingEntity;
import org.apache.hc.client5.http.entity.DeflateInputStreamFactory; import org.apache.hc.client5.http.entity.DeflateInputStreamFactory;
import org.apache.hc.client5.http.entity.GZIPInputStreamFactory; import org.apache.hc.client5.http.entity.GZIPInputStreamFactory;
@ -84,16 +86,32 @@ public final class ContentCompressionExec implements ExecChainHandler {
final List<String> acceptEncoding, final List<String> acceptEncoding,
final Lookup<InputStreamFactory> decoderRegistry, final Lookup<InputStreamFactory> decoderRegistry,
final boolean ignoreUnknown) { 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, this.acceptEncoding = MessageSupport.format(HttpHeaders.ACCEPT_ENCODING,
acceptEncoding != null ? acceptEncoding.toArray( acceptEncoding != null ? acceptEncoding.toArray(
EMPTY_STRING_ARRAY) : new String[] {"gzip", "x-gzip", "deflate"}); EMPTY_STRING_ARRAY) : encoding);
this.decoderRegistry = decoderRegistry != null ? decoderRegistry : if (decoderRegistry != null) {
RegistryBuilder.<InputStreamFactory>create() this.decoderRegistry = decoderRegistry;
} else {
final RegistryBuilder<InputStreamFactory> builder = RegistryBuilder.<InputStreamFactory>create()
.register("gzip", GZIPInputStreamFactory.getInstance()) .register("gzip", GZIPInputStreamFactory.getInstance())
.register("x-gzip", GZIPInputStreamFactory.getInstance()) .register("x-gzip", GZIPInputStreamFactory.getInstance())
.register("deflate", DeflateInputStreamFactory.getInstance()) .register("deflate", DeflateInputStreamFactory.getInstance());
.build(); if (brotliSupported) {
builder.register("br", BrotliInputStreamFactory.getInstance());
}
this.decoderRegistry = builder.build();
}
this.ignoreUnknown = ignoreUnknown; this.ignoreUnknown = ignoreUnknown;
} }
@ -107,6 +125,7 @@ public final class ContentCompressionExec implements ExecChainHandler {
* <ul> * <ul>
* <li>gzip - see {@link java.util.zip.GZIPInputStream}</li> * <li>gzip - see {@link java.util.zip.GZIPInputStream}</li>
* <li>deflate - see {@link org.apache.hc.client5.http.entity.DeflateInputStream}</li> * <li>deflate - see {@link org.apache.hc.client5.http.entity.DeflateInputStream}</li>
* <li>brotli - see {@link org.brotli.dec.BrotliInputStream}</li>
* </ul> * </ul>
*/ */
public ContentCompressionExec() { public ContentCompressionExec() {

View File

@ -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));
}
}

View File

@ -183,6 +183,22 @@ public class TestContentCompressionExec {
Assertions.assertTrue(entity instanceof StringEntity); 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 @Test
public void testUnknownContentEncoding() throws Exception { public void testUnknownContentEncoding() throws Exception {
final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/"); final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/");

View File

@ -65,6 +65,7 @@
<httpcore.version>5.2-beta1</httpcore.version> <httpcore.version>5.2-beta1</httpcore.version>
<log4j.version>2.17.0</log4j.version> <log4j.version>2.17.0</log4j.version>
<commons-codec.version>1.15</commons-codec.version> <commons-codec.version>1.15</commons-codec.version>
<brotli.version>0.1.2</brotli.version>
<conscrypt.version>2.5.2</conscrypt.version> <conscrypt.version>2.5.2</conscrypt.version>
<ehcache.version>3.9.6</ehcache.version> <ehcache.version>3.9.6</ehcache.version>
<memcached.version>2.12.3</memcached.version> <memcached.version>2.12.3</memcached.version>
@ -148,6 +149,11 @@
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version> <version>${commons-codec.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>${brotli.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.conscrypt</groupId> <groupId>org.conscrypt</groupId>
<artifactId>conscrypt-openjdk-uber</artifactId> <artifactId>conscrypt-openjdk-uber</artifactId>