Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,8 @@ public void handle(final ClassicHttpRequest request,
Assertions.assertEquals(new URIBuilder().setHttpHost(target).setPath("/random/100").build(),
reqWrapper.getUri());

assertThat(values.poll(), CoreMatchers.equalTo("gzip, x-gzip, deflate"));
assertThat(values.poll(), CoreMatchers.equalTo("gzip, x-gzip, deflate"));
assertThat(values.poll(), CoreMatchers.equalTo("gzip, deflate, lz4-framed, lz4-block, bzip2, pack200, deflate64, x-gzip"));
assertThat(values.poll(), CoreMatchers.equalTo("gzip, deflate, lz4-framed, lz4-block, bzip2, pack200, deflate64, x-gzip"));
assertThat(values.poll(), CoreMatchers.nullValue());
}

Expand Down
5 changes: 5 additions & 0 deletions httpclient5/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class BrotliInputStreamFactory implements InputStreamFactory {

/**
* Canonical token for the deflate content-coding.
* @since 5.6
*/
public static final String ENCODING = "br";

@Override
public String getContentEncoding() {
return ENCODING;
}

/**
* Default instance of {@link BrotliInputStreamFactory}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class DeflateInputStreamFactory implements InputStreamFactory {

/**
* Canonical token for the deflate content-coding.
* @since 5.6
*/
public static final String ENCODING = "deflate";

@Override
public String getContentEncoding() {
return ENCODING;
}

/**
* Default instance of {@link DeflateInputStreamFactory}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@
@Contract(threading = ThreadingBehavior.STATELESS)
public class GZIPInputStreamFactory implements InputStreamFactory {

/**
* Canonical token for the gzip content-coding.
* @since 5.6
*/
public static final String ENCODING = "gzip";

@Override
public String getContentEncoding() {
return ENCODING;
}

/**
* Default instance of {@link GZIPInputStreamFactory}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,21 @@ public interface InputStreamFactory {

InputStream create(InputStream inputStream) throws IOException;

/**
* Returns the canonical {@code Content-Encoding} token handled by this
* factory (for example {@code "gzip"}, {@code "deflate"}, {@code "br"}).
* <p>
* Implementations that do <strong>not</strong> represent a HTTP
* content-decoder should simply inherit the default implementation,
* which returns an empty string.
*
* @return the lower-case encoding token, or an empty string when the
* factory is not intended for HTTP content-decoding
*
* @since 5.6
*/
default String getContentEncoding() {
return "";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* ====================================================================
* 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.compress;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.hc.client5.http.entity.InputStreamFactory;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;

/**
* A factory for creating InputStream instances, utilizing Apache Commons Compress.
* This class is compiled with Commons Compress as an optional dependency, loading
* only when the library is present at runtime, avoiding mandatory inclusion in
* downstream builds.
* <p>
* <p>
* Some encodings require native helper JARs; runtime availability is checked
* using a lightweight Class.forName probe to register codecs only when helpers
* are present.
*
* @since 5.6
*/
@Internal
@Contract(threading = ThreadingBehavior.STATELESS)
final class CommonsCompressDecoderFactory implements InputStreamFactory {


/**
* Map of codings that need extra JARs → the fully‐qualified class we test for
*/
private static final Map<ContentCoding, String> REQUIRED_CLASS_NAME;

static {
final Map<ContentCoding, String> m = new EnumMap<>(ContentCoding.class);
m.put(ContentCoding.BROTLI, "org.brotli.dec.BrotliInputStream");
m.put(ContentCoding.ZSTD, "com.github.luben.zstd.ZstdInputStream");
m.put(ContentCoding.XZ, "org.tukaani.xz.XZInputStream");
m.put(ContentCoding.LZMA, "org.tukaani.xz.XZInputStream");
REQUIRED_CLASS_NAME = Collections.unmodifiableMap(m);
}

private final String encoding;

CommonsCompressDecoderFactory(final String encoding) {
this.encoding = encoding.toLowerCase(Locale.ROOT);
}

@Override
public String getContentEncoding() {
return encoding;
}

@Override
public InputStream create(final InputStream source) throws IOException {
try {
return new CompressorStreamFactory()
.createCompressorInputStream(encoding, source);
} catch (final CompressorException | LinkageError ex) {
throw new IOException(
"Unable to decode Content-Encoding '" + encoding + '\'', ex);
}
}


static boolean runtimeAvailable(final String token) {
final ContentCoding coding = ContentCoding.fromToken(token);
if (coding == null) {
return true;
}
final String helper = REQUIRED_CLASS_NAME.get(coding);
if (helper == null) {
// no extra JAR needed
return true;
}
try {
Class.forName(helper, false,
CommonsCompressDecoderFactory.class.getClassLoader());
return true;
} catch (final ClassNotFoundException | LinkageError ex) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* ====================================================================
* 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.compress;

import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
* Enumeration of the canonical IANA content-coding tokens supported by HttpClient for
* HTTP request and response bodies.
* <p>
* Each constant corresponds to the standard token used in the {@code Content-Encoding}
* and {@code Accept-Encoding} headers. Some codings (e.g. Brotli, Zstandard, XZ/LZMA)
* may require additional helper libraries at runtime.
*
* @since 5.6
*/
public enum ContentCoding {

/**
* GZIP compression format.
*/
GZIP("gzip"),
/**
* "deflate" compression format (zlib or raw).
*/
DEFLATE("deflate"),
/**
* Legacy alias for GZIP.
*/
X_GZIP("x-gzip"),

// Optional codecs requiring Commons-Compress or native helpers
/**
* Brotli compression format.
*/
BROTLI("br"),
/**
* Zstandard compression format.
*/
ZSTD("zstd"),
/**
* XZ compression format.
*/
XZ("xz"),
/**
* LZMA compression format.
*/
LZMA("lzma"),
/**
* Framed LZ4 compression format.
*/
LZ4_FRAMED("lz4-framed"),
/**
* Block LZ4 compression format.
*/
LZ4_BLOCK("lz4-block"),
/**
* BZIP2 compression format.
*/
BZIP2("bzip2"),
/**
* Pack200 compression format.
*/
PACK200("pack200"),
/**
* Deflate64 compression format.
*/
DEFLATE64("deflate64");

private static final Map<String, ContentCoding> TOKEN_LOOKUP;
static {
final Map<String, ContentCoding> map = new HashMap<>(values().length, 1f);
for (final ContentCoding contentCoding : values()) {
map.put(contentCoding.token, contentCoding);
}
TOKEN_LOOKUP = Collections.unmodifiableMap(map);
}

private final String token;

ContentCoding(final String token) {
this.token = token;
}

/**
* Returns the standard IANA token string for this content-coding.
*
* @return the lowercase token used in HTTP headers
*/
public String token() {
return token;
}

/**
* Lookup an enum by its token (case‐insensitive), or {@code null} if none matches.
* <p>
* This method is backed by a static, pre‐populated map so the lookup is O(1)
* instead of O(n).</p>
*
* @param token the content‐coding token to look up
* @return the matching enum constant, or {@code null} if none
*/
public static ContentCoding fromToken(final String token) {
return TOKEN_LOOKUP.get(
token == null ? null : token.toLowerCase(Locale.ROOT)
);
}
}
Loading