Skip to content

Commit 3015e29

Browse files
committed
Adds gzip handler to JDK http client based on header
Added new flag to JdkClientHttpRequest and created new DecompressingBodyHandler class to handle HttpClient response body decompression when encoded. Closes gh-35222 Signed-off-by: spicydev <[email protected]>
1 parent fbc5ff8 commit 3015e29

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequest.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
import java.net.http.HttpRequest;
2626
import java.net.http.HttpResponse;
2727
import java.net.http.HttpTimeoutException;
28+
import java.net.http.HttpResponse.BodyHandler;
29+
import java.net.http.HttpResponse.BodySubscriber;
30+
import java.net.http.HttpResponse.BodySubscribers;
31+
import java.net.http.HttpResponse.ResponseInfo;
2832
import java.nio.ByteBuffer;
2933
import java.time.Duration;
3034
import java.util.Collections;
@@ -37,6 +41,7 @@
3741
import java.util.concurrent.Executor;
3842
import java.util.concurrent.Flow;
3943
import java.util.concurrent.TimeUnit;
44+
import java.util.zip.GZIPInputStream;
4045

4146
import org.jspecify.annotations.Nullable;
4247

@@ -70,15 +75,18 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
7075

7176
private final @Nullable Duration timeout;
7277

78+
private final boolean compressionEnabled;
79+
7380

7481
public JdkClientHttpRequest(HttpClient httpClient, URI uri, HttpMethod method, Executor executor,
75-
@Nullable Duration readTimeout) {
82+
@Nullable Duration readTimeout, boolean compressionEnabled) {
7683

7784
this.httpClient = httpClient;
7885
this.uri = uri;
7986
this.method = method;
8087
this.executor = executor;
8188
this.timeout = readTimeout;
89+
this.compressionEnabled = compressionEnabled;
8290
}
8391

8492

@@ -98,7 +106,11 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
98106
CompletableFuture<HttpResponse<InputStream>> responseFuture = null;
99107
try {
100108
HttpRequest request = buildRequest(headers, body);
101-
responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
109+
if (compressionEnabled) {
110+
responseFuture = this.httpClient.sendAsync(request, new DecompressingBodyHandler());
111+
} else {
112+
responseFuture = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream());
113+
}
102114

103115
if (this.timeout != null) {
104116
TimeoutHandler timeoutHandler = new TimeoutHandler(responseFuture, this.timeout);
@@ -140,6 +152,10 @@ else if (cause instanceof IOException ioEx) {
140152

141153
private HttpRequest buildRequest(HttpHeaders headers, @Nullable Body body) {
142154
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(this.uri);
155+
156+
if (compressionEnabled) {
157+
headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
158+
}
143159

144160
headers.forEach((headerName, headerValues) -> {
145161
if (!DISALLOWED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
@@ -269,4 +285,30 @@ public void close() throws IOException {
269285
}
270286
}
271287

288+
/**
289+
* Custom BodyHandler that checks the Content-Encoding header and applies the appropriate decompression algorithm.
290+
*/
291+
public static final class DecompressingBodyHandler implements BodyHandler<InputStream> {
292+
293+
@Override
294+
public BodySubscriber<InputStream> apply(ResponseInfo responseInfo) {
295+
String contentEncoding = responseInfo.headers().firstValue(HttpHeaders.CONTENT_ENCODING).orElse("");
296+
if (contentEncoding.equalsIgnoreCase("gzip")) {
297+
// If the content is gzipped, wrap the InputStream with a GZIPInputStream
298+
return BodySubscribers.mapping(
299+
BodySubscribers.ofInputStream(),
300+
(InputStream is) -> {
301+
try {
302+
return new GZIPInputStream(is);
303+
} catch (IOException e) {
304+
throw new UncheckedIOException(e); // Propagate IOExceptions
305+
}
306+
});
307+
} else {
308+
// Otherwise, return a standard InputStream BodySubscriber
309+
return BodySubscribers.ofInputStream();
310+
}
311+
}
312+
}
313+
272314
}

spring-web/src/main/java/org/springframework/http/client/JdkClientHttpRequestFactory.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public class JdkClientHttpRequestFactory implements ClientHttpRequestFactory {
4343

4444
private @Nullable Duration readTimeout;
4545

46+
private boolean compressionEnabled;
47+
4648

4749
/**
4850
* Create a new instance of the {@code JdkClientHttpRequestFactory}
@@ -96,10 +98,18 @@ public void setReadTimeout(Duration readTimeout) {
9698
this.readTimeout = readTimeout;
9799
}
98100

101+
/**
102+
* Sets custom {@link BodyHandler} that can handle gzip encoded {@link HttpClient}'s response.
103+
* @param compressionEnabled to enable compression by default for all {@link HttpClient}'s requests.
104+
*/
105+
public void setCompressionEnabled(boolean compressionEnabled) {
106+
this.compressionEnabled = compressionEnabled;
107+
}
108+
99109

100110
@Override
101111
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
102-
return new JdkClientHttpRequest(this.httpClient, uri, httpMethod, this.executor, this.readTimeout);
112+
return new JdkClientHttpRequest(this.httpClient, uri, httpMethod, this.executor, this.readTimeout, this.compressionEnabled);
103113
}
104114

105115
}

0 commit comments

Comments
 (0)