Browse Source

toBodilessEntity() actually drains response body

See gh-24788
pull/24644/head
Rossen Stoyanchev 5 years ago
parent
commit
1822f272c7
  1. 2
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
  2. 55
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

2
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

@ -575,7 +575,7 @@ class DefaultWebClient implements WebClient { @@ -575,7 +575,7 @@ class DefaultWebClient implements WebClient {
public Mono<ResponseEntity<Void>> toBodilessEntity() {
return this.responseMono.flatMap(response ->
WebClientUtils.mapToEntity(response, handleBodyMono(response, Mono.<Void>empty()))
.doOnNext(entity -> response.releaseBody()) // body is drained in other cases
.flatMap(entity -> response.releaseBody().thenReturn(entity))
);
}

55
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

@ -31,16 +31,20 @@ import java.util.Arrays; @@ -31,16 +31,20 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.test.StepVerifier;
import org.springframework.core.ParameterizedTypeReference;
@ -297,6 +301,57 @@ class WebClientIntegrationTests { @@ -297,6 +301,57 @@ class WebClientIntegrationTests {
});
}
@Test // gh-24788
void retrieveJsonArrayAsBodilessEntityShouldReleasesConnection() {
// Constrain connection pool and make consecutive requests.
// 2nd request should hang if response was not drained.
ConnectionProvider connectionProvider = ConnectionProvider.create("test", 1);
this.server = new MockWebServer();
WebClient webClient = WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create(connectionProvider)))
.baseUrl(this.server.url("/").toString())
.build();
for (int i=1 ; i <= 2; i++) {
// Response must be large enough to circumvent eager prefetching
String json = Flux.just("{\"bar\":\"bar\",\"foo\":\"foo\"}")
.repeat(100)
.collect(Collectors.joining(",", "[", "]"))
.block();
prepareResponse(response -> response
.setHeader("Content-Type", "application/json")
.setBody(json));
Mono<ResponseEntity<Void>> result = webClient.get()
.uri("/json").accept(MediaType.APPLICATION_JSON)
.retrieve()
.toBodilessEntity();
StepVerifier.create(result)
.consumeNextWith(entity -> {
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(entity.getHeaders().getContentLength()).isEqualTo(2627);
assertThat(entity.getBody()).isNull();
})
.expectComplete()
.verify(Duration.ofSeconds(3));
expectRequestCount(i);
expectRequest(request -> {
assertThat(request.getPath()).isEqualTo("/json");
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo("application/json");
});
}
}
@ParameterizedWebClientTest
void retrieveJsonAsSerializedText(ClientHttpConnector connector) {
startServer(connector);

Loading…
Cancel
Save