add br decompression support (#363)
This commit is contained in:
parent
35732cacb2
commit
db47570efe
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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, "/");
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue