diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java index ce589bf726..428b14e098 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java @@ -41,14 +41,26 @@ import org.springframework.web.reactive.function.BodyExtractor; /** * Represents an HTTP response, as returned by {@link WebClient} and also - * {@link ExchangeFunction}. Provides access to the response status and headers, - * and also methods to consume the response body. + * {@link ExchangeFunction}. Provides access to the response status and + * headers, and also methods to consume the response body. * - *

NOTE: When given access to a {@link ClientResponse}, + *

NOTE: When using a {@link ClientResponse} * through the {@code WebClient} * {@link WebClient.RequestHeadersSpec#exchange() exchange()} method, - * you must always use one of the body or toEntity methods to ensure resources - * are released and avoid potential issues with HTTP connection pooling. + * you have to make sure that the body is consumed or released by using + * one of the following methods: + *

* You can use {@code bodyToMono(Void.class)} if no response content is * expected. However keep in mind that if the response does have content, the * connection will be closed and will not be placed back in the pool. @@ -132,6 +144,14 @@ public interface ClientResponse { */ Flux bodyToFlux(ParameterizedTypeReference elementTypeRef); + /** + * Releases the body of this response. + * @return a completion signal + * @since 5.2 + * @see org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) + */ + Mono releaseBody(); + /** * Return this response as a delayed {@code ResponseEntity}. * @param bodyClass the expected response body type diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java index 395f785be8..18eff19e79 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java @@ -155,6 +155,13 @@ class DefaultClientResponse implements ClientResponse { return body(BodyExtractors.toFlux(elementTypeRef)); } + @Override + public Mono releaseBody() { + return body(BodyExtractors.toDataBuffers()) + .map(DataBufferUtils::release) + .then(); + } + @Override public Mono> toEntity(Class bodyType) { return WebClientUtils.toEntity(this, bodyToMono(bodyType)); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java index d1d3c6a59c..045e0bd6b0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/ClientResponseWrapper.java @@ -118,6 +118,11 @@ public class ClientResponseWrapper implements ClientResponse { return this.delegate.bodyToFlux(elementTypeRef); } + @Override + public Mono releaseBody() { + return this.delegate.releaseBody(); + } + @Override public Mono> toEntity(Class bodyType) { return this.delegate.toEntity(bodyType); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java index cc3213ab30..397ae88102 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java @@ -166,6 +166,25 @@ public class WebClientDataBufferAllocatingTests extends AbstractDataBufferAlloca }); } + @ParameterizedDataBufferAllocatingTest + public void releaseBody(String displayName, DataBufferFactory bufferFactory) { + super.bufferFactory = bufferFactory; + + this.server.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader("Content-Type", "text/plain") + .setBody("foo bar")); + + Mono result = this.webClient.get() + .exchange() + .flatMap(ClientResponse::releaseBody); + + + StepVerifier.create(result) + .expectComplete() + .verify(Duration.ofSeconds(3)); + } + private void testOnStatus(Throwable expected, Function> exceptionFunction) {