Add close() method on HTTP client response
Before this commit, there was no way to signal the HTTP client that we were done consuming the response. Without that, the underlying client library cannot know when it is safe to release the associated resources (e.g. the HTTP connection). This commit adds new `close()` methods on both `ClientHttpResponse` and `ClientResponse`. This methods is non-blocking and its behavior depends on the library, its configuration, HTTP version, etc. At the `WebClient` level, `close()` is called automatically if we consume the response body through the `ResponseSpec` or the `ClientResponse` itself. Note that it is *required* to call `close()` manually otherwise; not doing so might create resource leaks or connection issues. Issue: SPR-15920
This commit is contained in:
@@ -70,6 +70,12 @@ public class WebClientIntegrationTests {
|
||||
public void headers() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
this.webClient.get().uri("/test")
|
||||
.exchange()
|
||||
.map(response -> {
|
||||
response.close();
|
||||
return response.statusCode();
|
||||
});
|
||||
Mono<HttpHeaders> result = this.webClient.get()
|
||||
.uri("/greeting?name=Spring")
|
||||
.exchange()
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.mock.http.client.reactive.test.MockClientHttpResponse;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Mock tests using a {@link ExchangeFunction} through {@link WebClient}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class WebClientMockTests {
|
||||
|
||||
private MockClientHttpResponse response;
|
||||
|
||||
private ClientHttpConnector mockConnector;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.mockConnector = mock(ClientHttpConnector.class);
|
||||
this.webClient = WebClient.builder().clientConnector(this.mockConnector).build();
|
||||
this.response = new MockClientHttpResponse(HttpStatus.OK);
|
||||
this.response.setBody("example");
|
||||
|
||||
given(this.mockConnector.connect(any(), any(), any())).willReturn(Mono.just(this.response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseManually() {
|
||||
Mono<HttpHeaders> headers= this.webClient
|
||||
.get().uri("/test")
|
||||
.exchange()
|
||||
.map(response -> response.headers().asHttpHeaders());
|
||||
StepVerifier.create(headers)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
assertFalse(this.response.isClosed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseExchangeMono() {
|
||||
Mono<String> body = this.webClient
|
||||
.get().uri("/test")
|
||||
.exchange()
|
||||
.flatMap(response -> response.bodyToMono(String.class));
|
||||
StepVerifier.create(body)
|
||||
.expectNext("example")
|
||||
.verifyComplete();
|
||||
assertTrue(this.response.isClosed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseExchangeFlux() {
|
||||
Flux<String> body = this.webClient
|
||||
.get().uri("/test")
|
||||
.exchange()
|
||||
.flatMapMany(response -> response.bodyToFlux(String.class));
|
||||
StepVerifier.create(body)
|
||||
.expectNext("example")
|
||||
.verifyComplete();
|
||||
assertTrue(this.response.isClosed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseExchangeEntity() {
|
||||
ResponseEntity<String> entity = this.webClient
|
||||
.get().uri("/test")
|
||||
.exchange()
|
||||
.flatMap(response -> response.toEntity(String.class))
|
||||
.block();
|
||||
assertEquals("example", entity.getBody());
|
||||
assertTrue(this.response.isClosed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseRetrieveMono() {
|
||||
Mono<String> body = this.webClient
|
||||
.get().uri("/test")
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
StepVerifier.create(body)
|
||||
.expectNext("example")
|
||||
.verifyComplete();
|
||||
assertTrue(this.response.isClosed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDisposeResponseRetrieveFlux() {
|
||||
Flux<String> body = this.webClient
|
||||
.get().uri("/test")
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class);
|
||||
StepVerifier.create(body)
|
||||
.expectNext("example")
|
||||
.verifyComplete();
|
||||
assertTrue(this.response.isClosed());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user