From 8d6117e419058addcaee5a45370f0276b28602e5 Mon Sep 17 00:00:00 2001 From: Daniil Razorenov Date: Mon, 23 Jun 2025 22:54:28 +0500 Subject: [PATCH 1/2] Support StreamingHttpOutputMessage in RestClient This commit allows RestClient to handle StreamingHttpOutputMessage properly by checking the type of the request and invoking setBody() when appropriate. This improves interoperability with components that expect streamed output. A new integration test has been added to verify the functionality. See gh-35102 Signed-off-by: Daniil Razorenov --- .../web/client/DefaultRestClient.java | 9 +++++++- .../client/RestClientIntegrationTests.java | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 95e485d5b10..36ce64db342 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -466,7 +466,14 @@ final class DefaultRestClient implements RestClient { @Override public RequestBodySpec body(StreamingHttpOutputMessage.Body body) { - this.body = request -> body.writeTo(request.getBody()); + this.body = request -> { + if (request instanceof StreamingHttpOutputMessage streamingMessage) { + streamingMessage.setBody(body); + } + else { + body.writeTo(request.getBody()); + } + }; return this; } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index c1a726181d6..4fa39300121 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.web.client; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -529,6 +530,27 @@ class RestClientIntegrationTests { }); } + @ParameterizedRestClientTest + void postStreamingMessageBody(ClientHttpRequestFactory requestFactory) { + startServer(requestFactory); + + prepareResponse(response -> response.setResponseCode(200)); + + ResponseEntity result = this.restClient.post() + .uri("/streaming/body") + .body(new ByteArrayInputStream("test-data".getBytes(UTF_8))::transferTo) + .retrieve() + .toBodilessEntity(); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + + expectRequestCount(1); + expectRequest(request -> { + assertThat(request.getPath()).isEqualTo("/streaming/body"); + assertThat(request.getBody().readUtf8()).isEqualTo("test-data"); + }); + } + @ParameterizedRestClientTest // gh-31361 public void postForm(ClientHttpRequestFactory requestFactory) { startServer(requestFactory); From f84552a97ea9ac098bc25d2d188cd1cb89b19505 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 25 Jun 2025 12:31:44 +0100 Subject: [PATCH 2/2] Polishing contribution Closes gh-35102 --- .../client/RestClientIntegrationTests.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index 4fa39300121..fe1c9e07fb0 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -43,6 +43,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.StreamingHttpOutputMessage; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; @@ -52,6 +53,7 @@ import org.springframework.http.client.JettyClientHttpRequestFactory; import org.springframework.http.client.ReactorClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.CollectionUtils; +import org.springframework.util.FastByteArrayOutputStream; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.testfixture.xml.Pojo; @@ -530,27 +532,6 @@ class RestClientIntegrationTests { }); } - @ParameterizedRestClientTest - void postStreamingMessageBody(ClientHttpRequestFactory requestFactory) { - startServer(requestFactory); - - prepareResponse(response -> response.setResponseCode(200)); - - ResponseEntity result = this.restClient.post() - .uri("/streaming/body") - .body(new ByteArrayInputStream("test-data".getBytes(UTF_8))::transferTo) - .retrieve() - .toBodilessEntity(); - - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - - expectRequestCount(1); - expectRequest(request -> { - assertThat(request.getPath()).isEqualTo("/streaming/body"); - assertThat(request.getBody().readUtf8()).isEqualTo("test-data"); - }); - } - @ParameterizedRestClientTest // gh-31361 public void postForm(ClientHttpRequestFactory requestFactory) { startServer(requestFactory); @@ -594,6 +575,30 @@ class RestClientIntegrationTests { }); } + @ParameterizedRestClientTest // gh-35102 + void postStreamingBody(ClientHttpRequestFactory requestFactory) { + startServer(requestFactory); + prepareResponse(response -> response.setResponseCode(200)); + + StreamingHttpOutputMessage.Body testBody = out -> { + assertThat(out).as("Not a streaming response").isNotInstanceOf(FastByteArrayOutputStream.class); + new ByteArrayInputStream("test-data".getBytes(UTF_8)).transferTo(out); + }; + + ResponseEntity result = this.restClient.post() + .uri("/streaming/body") + .body(testBody) + .retrieve() + .toBodilessEntity(); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + + expectRequestCount(1); + expectRequest(request -> { + assertThat(request.getPath()).isEqualTo("/streaming/body"); + assertThat(request.getBody().readUtf8()).isEqualTo("test-data"); + }); + } @ParameterizedRestClientTest void statusHandler(ClientHttpRequestFactory requestFactory) {