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:
Brian Clozel
2017-09-06 14:06:56 +02:00
parent ba6b617bd5
commit 16f3f8d28f
11 changed files with 238 additions and 16 deletions

View File

@@ -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()

View File

@@ -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());
}
}