Skip to content

Commit d20e827

Browse files
committed
Adds gzip handler to JDK http client based on header
1 parent fbc5ff8 commit d20e827

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)