Skip to content

Commit 5accc21

Browse files
committed
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
1 parent b85993c commit 5accc21

4 files changed

Lines changed: 31 additions & 16 deletions

File tree

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.concurrent.Flow;
4444
import java.util.concurrent.TimeUnit;
4545
import java.util.concurrent.atomic.AtomicBoolean;
46+
import java.util.function.Consumer;
4647
import java.util.zip.GZIPInputStream;
4748
import java.util.zip.InflaterInputStream;
4849

@@ -113,16 +114,15 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
113114
try {
114115
HttpRequest request = buildRequest(headers, body);
115116
responseFuture = this.httpClient.sendAsync(request, this.compression ? new DecompressingBodyHandler() : HttpResponse.BodyHandlers.ofInputStream());
116-
117117
if (this.timeout != null) {
118118
timeoutHandler = new TimeoutHandler(responseFuture, this.timeout);
119119
HttpResponse<InputStream> response = responseFuture.get();
120120
InputStream inputStream = timeoutHandler.wrapInputStream(response);
121-
return new JdkClientHttpResponse(response, inputStream);
121+
return new JdkClientHttpResponse(response, processResponseHeaders(), inputStream);
122122
}
123123
else {
124124
HttpResponse<InputStream> response = responseFuture.get();
125-
return new JdkClientHttpResponse(response, response.body());
125+
return new JdkClientHttpResponse(response, processResponseHeaders(), response.body());
126126
}
127127
}
128128
catch (InterruptedException ex) {
@@ -231,6 +231,19 @@ private static Set<String> disallowedHeaders() {
231231
return Collections.unmodifiableSet(headers);
232232
}
233233

234+
private Consumer<HttpHeaders> processResponseHeaders() {
235+
if (this.compression) {
236+
return headers -> {
237+
String encoding = headers.getFirst(HttpHeaders.CONTENT_ENCODING);
238+
if (encoding != null && SUPPORTED_ENCODINGS.contains(encoding)) {
239+
headers.remove(HttpHeaders.CONTENT_ENCODING);
240+
headers.remove(HttpHeaders.CONTENT_LENGTH);
241+
}
242+
};
243+
}
244+
return headers -> {};
245+
}
246+
234247

235248
private static final class ByteBufferMapper implements OutputStreamPublisher.ByteMapper<ByteBuffer> {
236249

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,14 @@
2121
import java.net.http.HttpClient;
2222
import java.net.http.HttpResponse;
2323
import java.util.List;
24-
import java.util.Locale;
2524
import java.util.Map;
25+
import java.util.function.Consumer;
2626

2727
import org.jspecify.annotations.Nullable;
2828

2929
import org.springframework.http.HttpHeaders;
3030
import org.springframework.http.HttpStatus;
3131
import org.springframework.http.HttpStatusCode;
32-
import org.springframework.util.CollectionUtils;
33-
import org.springframework.util.LinkedCaseInsensitiveMap;
34-
import org.springframework.util.MultiValueMap;
3532
import org.springframework.util.StreamUtils;
3633

3734
/**
@@ -50,18 +47,18 @@ class JdkClientHttpResponse implements ClientHttpResponse {
5047
private final InputStream body;
5148

5249

53-
public JdkClientHttpResponse(HttpResponse<InputStream> response, @Nullable InputStream body) {
50+
JdkClientHttpResponse(HttpResponse<InputStream> response, Consumer<HttpHeaders> headersConsumer, @Nullable InputStream body) {
5451
this.response = response;
55-
this.headers = adaptHeaders(response);
52+
this.headers = adaptHeaders(response, headersConsumer);
5653
this.body = (body != null ? body : InputStream.nullInputStream());
5754
}
5855

59-
private static HttpHeaders adaptHeaders(HttpResponse<?> response) {
56+
private static HttpHeaders adaptHeaders(HttpResponse<?> response, Consumer<HttpHeaders> headersConsumer) {
6057
Map<String, List<String>> rawHeaders = response.headers().map();
61-
Map<String, List<String>> map = new LinkedCaseInsensitiveMap<>(rawHeaders.size(), Locale.ROOT);
62-
MultiValueMap<String, String> multiValueMap = CollectionUtils.toMultiValueMap(map);
63-
multiValueMap.putAll(rawHeaders);
64-
return HttpHeaders.readOnlyHttpHeaders(multiValueMap);
58+
HttpHeaders headers = new HttpHeaders();
59+
rawHeaders.forEach(headers::put);
60+
headersConsumer.accept(headers);
61+
return HttpHeaders.readOnlyHttpHeaders(headers);
6562
}
6663

6764

spring-web/src/test/java/org/springframework/http/client/AbstractMockWebServerTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ else if(request.getTarget().startsWith("/compress/") && request.getBody() != nul
136136
.body(buffer)
137137
.code(200);
138138
builder.setHeader(HttpHeaders.CONTENT_ENCODING, encoding);
139+
builder.setHeader(HttpHeaders.CONTENT_LENGTH, buffer.size());
139140
return builder.build();
140141
}
141142
return new MockResponse.Builder().code(404).build();

spring-web/src/test/java/org/springframework/http/client/JdkClientHttpRequestFactoryTests.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ void compressionGzip() throws IOException {
135135
try (ClientHttpResponse response = request.execute()) {
136136
assertThat(response.getStatusCode()).as("Invalid response status").isEqualTo(HttpStatus.OK);
137137
assertThat(response.getHeaders().getFirst("Content-Encoding"))
138-
.as("Invalid content encoding").isEqualTo("gzip");
138+
.as("Content Encoding should be removed").isNull();
139+
assertThat(response.getHeaders().getFirst("Content-Length"))
140+
.as("Content-Length should be removed").isNull();
139141
assertThat(response.getBody()).as("Invalid request body").hasContent("Payload to compress");
140142
}
141143
}
@@ -150,7 +152,9 @@ void compressionDeflate() throws IOException {
150152
try (ClientHttpResponse response = request.execute()) {
151153
assertThat(response.getStatusCode()).as("Invalid response status").isEqualTo(HttpStatus.OK);
152154
assertThat(response.getHeaders().getFirst("Content-Encoding"))
153-
.as("Invalid content encoding").isEqualTo("deflate");
155+
.as("Content Encoding should be removed").isNull();
156+
assertThat(response.getHeaders().getFirst("Content-Length"))
157+
.as("Content-Length should be removed").isNull();
154158
assertThat(response.getBody()).as("Invalid request body").hasContent("Payload to compress");
155159
}
156160
}

0 commit comments

Comments
 (0)