24
24
import java .net .http .HttpClient ;
25
25
import java .net .http .HttpRequest ;
26
26
import java .net .http .HttpResponse ;
27
- import java .net .http .HttpTimeoutException ;
28
27
import java .net .http .HttpResponse .BodyHandler ;
29
28
import java .net .http .HttpResponse .BodySubscriber ;
30
29
import java .net .http .HttpResponse .BodySubscribers ;
31
30
import java .net .http .HttpResponse .ResponseInfo ;
31
+ import java .net .http .HttpTimeoutException ;
32
32
import java .nio .ByteBuffer ;
33
33
import java .time .Duration ;
34
34
import java .util .Collections ;
35
+ import java .util .List ;
35
36
import java .util .Locale ;
36
37
import java .util .Set ;
37
38
import java .util .TreeSet ;
42
43
import java .util .concurrent .Flow ;
43
44
import java .util .concurrent .TimeUnit ;
44
45
import java .util .zip .GZIPInputStream ;
46
+ import java .util .zip .InflaterInputStream ;
45
47
46
48
import org .jspecify .annotations .Nullable ;
47
49
@@ -64,6 +66,8 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
64
66
65
67
private static final Set <String > DISALLOWED_HEADERS = disallowedHeaders ();
66
68
69
+ private static final List <String > ALLOWED_ENCODINGS = List .of ("gzip" , "deflate" );
70
+
67
71
68
72
private final HttpClient httpClient ;
69
73
@@ -106,11 +110,7 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
106
110
CompletableFuture <HttpResponse <InputStream >> responseFuture = null ;
107
111
try {
108
112
HttpRequest request = buildRequest (headers , body );
109
- if (compressionEnabled ) {
110
- responseFuture = this .httpClient .sendAsync (request , new DecompressingBodyHandler ());
111
- } else {
112
- responseFuture = this .httpClient .sendAsync (request , HttpResponse .BodyHandlers .ofInputStream ());
113
- }
113
+ responseFuture = this .httpClient .sendAsync (request , new DecompressingBodyHandler ());
114
114
115
115
if (this .timeout != null ) {
116
116
TimeoutHandler timeoutHandler = new TimeoutHandler (responseFuture , this .timeout );
@@ -152,8 +152,13 @@ else if (cause instanceof IOException ioEx) {
152
152
153
153
private HttpRequest buildRequest (HttpHeaders headers , @ Nullable Body body ) {
154
154
HttpRequest .Builder builder = HttpRequest .newBuilder ().uri (this .uri );
155
-
156
- if (compressionEnabled ) {
155
+
156
+ // When compression is enabled and valid encoding is absent, we add gzip as standard encoding
157
+ if (this .compressionEnabled ) {
158
+ if (headers .containsHeader (HttpHeaders .ACCEPT_ENCODING ) &&
159
+ !ALLOWED_ENCODINGS .contains (headers .getFirst (HttpHeaders .ACCEPT_ENCODING ))) {
160
+ headers .remove (HttpHeaders .ACCEPT_ENCODING );
161
+ }
157
162
headers .add (HttpHeaders .ACCEPT_ENCODING , "gzip" );
158
163
}
159
164
@@ -242,7 +247,7 @@ public ByteBuffer map(byte[] b, int off, int len) {
242
247
/**
243
248
* Temporary workaround to use instead of {@link HttpRequest.Builder#timeout(Duration)}
244
249
* until <a href="https://bugs.openjdk.org/browse/JDK-8258397">JDK-8258397</a>
245
- * is fixed. Essentially, create a future wiht a timeout handler, and use it
250
+ * is fixed. Essentially, create a future with a timeout handler, and use it
246
251
* to close the response.
247
252
* @see <a href="https://mail.openjdk.org/pipermail/net-dev/2021-October/016672.html">OpenJDK discussion thread</a>
248
253
*/
@@ -287,6 +292,7 @@ public void close() throws IOException {
287
292
288
293
/**
289
294
* Custom BodyHandler that checks the Content-Encoding header and applies the appropriate decompression algorithm.
295
+ * Supports Gzip and Deflate encoded responses.
290
296
*/
291
297
public static final class DecompressingBodyHandler implements BodyHandler <InputStream > {
292
298
@@ -300,11 +306,19 @@ public BodySubscriber<InputStream> apply(ResponseInfo responseInfo) {
300
306
(InputStream is ) -> {
301
307
try {
302
308
return new GZIPInputStream (is );
303
- } catch (IOException e ) {
304
- throw new UncheckedIOException (e ); // Propagate IOExceptions
309
+ }
310
+ catch (IOException ex ) {
311
+ throw new UncheckedIOException (ex ); // Propagate IOExceptions
305
312
}
306
313
});
307
- } else {
314
+ }
315
+ else if (contentEncoding .equalsIgnoreCase ("deflate" )) {
316
+ // If the content is encoded using deflate, wrap the InputStream with a InflaterInputStream
317
+ return BodySubscribers .mapping (
318
+ BodySubscribers .ofInputStream (),
319
+ InflaterInputStream ::new );
320
+ }
321
+ else {
308
322
// Otherwise, return a standard InputStream BodySubscriber
309
323
return BodySubscribers .ofInputStream ();
310
324
}
0 commit comments