Clean HTTP response headers after decompression in JDK client

Prior to this commit, gh-35225 introduced HTTP response body
decompression support for "gzip" and "deflate" encodings for the
`JdkClientHttpRequestFactory`.

While body decompression works, the client keeps the "Content-Encoding"
and "Content-Length" response headers intact, which misleads further
response handling: the body size has changed and it is not compressed
anymore.

This commit ensures that the relevant response headers are removed from
the HTTP response after decompression.

Fixes gh-35668
This commit is contained in:
Brian Clozel
2025-10-20 11:38:25 +02:00
parent b85993c7cb
commit 5accc21223
4 changed files with 31 additions and 16 deletions
@@ -43,6 +43,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Flow;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
@@ -113,16 +114,15 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
try {
HttpRequest request = buildRequest(headers, body);
responseFuture = this.httpClient.sendAsync(request, this.compression ? new DecompressingBodyHandler() : HttpResponse.BodyHandlers.ofInputStream());
if (this.timeout != null) {
timeoutHandler = new TimeoutHandler(responseFuture, this.timeout);
HttpResponse<InputStream> response = responseFuture.get();
InputStream inputStream = timeoutHandler.wrapInputStream(response);
return new JdkClientHttpResponse(response, inputStream);
return new JdkClientHttpResponse(response, processResponseHeaders(), inputStream);
}
else {
HttpResponse<InputStream> response = responseFuture.get();
return new JdkClientHttpResponse(response, response.body());
return new JdkClientHttpResponse(response, processResponseHeaders(), response.body());
}
}
catch (InterruptedException ex) {
@@ -231,6 +231,19 @@ class JdkClientHttpRequest extends AbstractStreamingClientHttpRequest {
return Collections.unmodifiableSet(headers);
}
private Consumer<HttpHeaders> processResponseHeaders() {
if (this.compression) {
return headers -> {
String encoding = headers.getFirst(HttpHeaders.CONTENT_ENCODING);
if (encoding != null && SUPPORTED_ENCODINGS.contains(encoding)) {
headers.remove(HttpHeaders.CONTENT_ENCODING);
headers.remove(HttpHeaders.CONTENT_LENGTH);
}
};
}
return headers -> {};
}
private static final class ByteBufferMapper implements OutputStreamPublisher.ByteMapper<ByteBuffer> {
@@ -21,17 +21,14 @@ import java.io.InputStream;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
/**
@@ -50,18 +47,18 @@ class JdkClientHttpResponse implements ClientHttpResponse {
private final InputStream body;
public JdkClientHttpResponse(HttpResponse<InputStream> response, @Nullable InputStream body) {
JdkClientHttpResponse(HttpResponse<InputStream> response, Consumer<HttpHeaders> headersConsumer, @Nullable InputStream body) {
this.response = response;
this.headers = adaptHeaders(response);
this.headers = adaptHeaders(response, headersConsumer);
this.body = (body != null ? body : InputStream.nullInputStream());
}
private static HttpHeaders adaptHeaders(HttpResponse<?> response) {
private static HttpHeaders adaptHeaders(HttpResponse<?> response, Consumer<HttpHeaders> headersConsumer) {
Map<String, List<String>> rawHeaders = response.headers().map();
Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ROOT);
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
multiValueMap.putAll(rawHeaders);
return HttpHeaders.readOnlyHttpHeaders(multiValueMap);
HttpHeaders headers = new HttpHeaders();
rawHeaders.forEach(headers::put);
headersConsumer.accept(headers);
return HttpHeaders.readOnlyHttpHeaders(headers);
}