Skip to content

Commit 47c5cd2

Browse files
committed
Add missing "Content-Length: 0" header with HttpComponents
Prior to this commit, HTTP requests sent with the `HttpComponentsClientHttpRequestFactory` would not set a "Content-Length" header for empty request bodies. Setting a request entity is the expected behavior for unsafe HTTP methods, and this would align the behavior with other HTTP clients. Developers would often rely on `BufferingClientHttpRequestFactory` to set this information on the request. This commit ensures that a `NullEntity` is used for unsafe HTTP methods, when no body has been set for the request. This result in a "Content-Length:0" request header. Fixes gh-32678
1 parent 5a24e94 commit 47c5cd2

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.apache.hc.core5.http.ClassicHttpResponse;
3131
import org.apache.hc.core5.http.Header;
3232
import org.apache.hc.core5.http.HttpEntity;
33+
import org.apache.hc.core5.http.Method;
34+
import org.apache.hc.core5.http.io.entity.NullEntity;
3335
import org.apache.hc.core5.http.protocol.HttpContext;
3436

3537
import org.springframework.http.HttpHeaders;
@@ -89,8 +91,10 @@ protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body
8991
addHeaders(this.httpRequest, headers);
9092

9193
if (body != null) {
92-
HttpEntity requestEntity = new BodyEntity(headers, body);
93-
this.httpRequest.setEntity(requestEntity);
94+
this.httpRequest.setEntity(new BodyEntity(headers, body));
95+
}
96+
else if (!Method.isSafe(this.httpRequest.getMethod())) {
97+
this.httpRequest.setEntity(NullEntity.INSTANCE);
9498
}
9599
ClassicHttpResponse httpResponse = this.httpClient.executeOpen(null, this.httpRequest, this.httpContext);
96100
return new HttpComponentsClientHttpResponse(httpResponse);

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

+4
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ else if(request.getPath().startsWith("/methods/")) {
106106
assertThat(request.getMethod()).isEqualTo(expectedMethod);
107107
return new MockResponse();
108108
}
109+
else if(request.getPath().startsWith("/header/")) {
110+
String headerName = request.getPath().replace("/header/","");
111+
return new MockResponse().setBody(headerName + ":" + request.getHeader(headerName)).setResponseCode(200);
112+
}
109113
return new MockResponse().setResponseCode(404);
110114
}
111115
catch (Throwable exc) {

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

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.http.client;
1818

19+
import java.io.InputStreamReader;
1920
import java.net.URI;
21+
import java.util.stream.Stream;
2022

2123
import org.apache.hc.client5.http.classic.HttpClient;
2224
import org.apache.hc.client5.http.config.Configurable;
@@ -26,8 +28,12 @@
2628
import org.apache.hc.client5.http.protocol.HttpClientContext;
2729
import org.apache.hc.core5.util.Timeout;
2830
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.params.ParameterizedTest;
32+
import org.junit.jupiter.params.provider.MethodSource;
2933

3034
import org.springframework.http.HttpMethod;
35+
import org.springframework.http.HttpStatus;
36+
import org.springframework.util.FileCopyUtils;
3137

3238
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3339
import static org.assertj.core.api.Assertions.assertThat;
@@ -37,6 +43,7 @@
3743

3844
/**
3945
* @author Stephane Nicoll
46+
* @author Brian Clozel
4047
*/
4148
class HttpComponentsClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests {
4249

@@ -145,6 +152,36 @@ public HttpClient getHttpClient() {
145152
assertThat(requestConfig2.getConnectionRequestTimeout()).isEqualTo(Timeout.of(7000, MILLISECONDS));
146153
}
147154

155+
@ParameterizedTest
156+
@MethodSource("unsafeHttpMethods")
157+
void shouldSetContentLengthWhenEmptyBody(HttpMethod method) throws Exception {
158+
ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/header/Content-Length"), method);
159+
try (ClientHttpResponse response = request.execute()) {
160+
assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
161+
String result = FileCopyUtils.copyToString(new InputStreamReader(response.getBody()));
162+
assertThat(result).as("Invalid body").isEqualTo("Content-Length:0");
163+
}
164+
}
165+
166+
static Stream<HttpMethod> unsafeHttpMethods() {
167+
return Stream.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.PATCH);
168+
}
169+
170+
@ParameterizedTest
171+
@MethodSource("safeHttpMethods")
172+
void shouldNotSetContentLengthWhenEmptyBodyAndSafeMethod(HttpMethod method) throws Exception {
173+
ClientHttpRequest request = factory.createRequest(URI.create(baseUrl + "/header/Content-Length"), method);
174+
try (ClientHttpResponse response = request.execute()) {
175+
assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
176+
String result = FileCopyUtils.copyToString(new InputStreamReader(response.getBody()));
177+
assertThat(result).as("Invalid body").isEqualTo("Content-Length:null");
178+
}
179+
}
180+
181+
static Stream<HttpMethod> safeHttpMethods() {
182+
return Stream.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.TRACE);
183+
}
184+
148185
private RequestConfig retrieveRequestConfig(HttpComponentsClientHttpRequestFactory factory) throws Exception {
149186
URI uri = URI.create(baseUrl + "/status/ok");
150187
HttpComponentsClientHttpRequest request = (HttpComponentsClientHttpRequest)

0 commit comments

Comments
 (0)