Add ResponseSpec#toEntityFlux overload with BodyExtractor
See gh-26114
This commit is contained in:
parent
05e3f271b6
commit
922d5d271a
|
|
@ -43,11 +43,13 @@ import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.reactive.function.BodyInserter;
|
import org.springframework.web.reactive.function.BodyInserter;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
|
|
@ -599,6 +601,12 @@ class DefaultWebClient implements WebClient {
|
||||||
handlerEntityFlux(response, response.bodyToFlux(elementTypeRef)));
|
handlerEntityFlux(response, response.bodyToFlux(elementTypeRef)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(BodyExtractor<Flux<T>, ? super ClientHttpResponse> bodyExtractor) {
|
||||||
|
return this.responseMono.flatMap(response ->
|
||||||
|
handlerEntityFlux(response, response.body(bodyExtractor)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ResponseEntity<Void>> toBodilessEntity() {
|
public Mono<ResponseEntity<Void>> toBodilessEntity() {
|
||||||
return this.responseMono.flatMap(response ->
|
return this.responseMono.flatMap(response ->
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,10 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||||
import org.springframework.http.codec.ClientCodecConfigurer;
|
import org.springframework.http.codec.ClientCodecConfigurer;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.reactive.function.BodyExtractor;
|
||||||
import org.springframework.web.reactive.function.BodyInserter;
|
import org.springframework.web.reactive.function.BodyInserter;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||||
|
|
@ -889,7 +891,7 @@ public interface WebClient {
|
||||||
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(Class<T> elementType);
|
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(Class<T> elementType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
|
* Variant of {@link #toEntityFlux(Class)} with a {@link ParameterizedTypeReference}.
|
||||||
* @param elementTypeReference the type of element to decode the target Flux to
|
* @param elementTypeReference the type of element to decode the target Flux to
|
||||||
* @param <T> the body element type
|
* @param <T> the body element type
|
||||||
* @return the {@code ResponseEntity}
|
* @return the {@code ResponseEntity}
|
||||||
|
|
@ -897,6 +899,15 @@ public interface WebClient {
|
||||||
*/
|
*/
|
||||||
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(ParameterizedTypeReference<T> elementTypeReference);
|
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(ParameterizedTypeReference<T> elementTypeReference);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variant of {@link #toEntityFlux(Class)} with a {@link BodyExtractor}.
|
||||||
|
* @param bodyExtractor the {@code BodyExtractor} that reads from the response
|
||||||
|
* @param <T> the body element type
|
||||||
|
* @return the {@code ResponseEntity}
|
||||||
|
* @since 5.3.2
|
||||||
|
*/
|
||||||
|
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(BodyExtractor<Flux<T>, ? super ClientHttpResponse> bodyExtractor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@code ResponseEntity} without a body. For an error response
|
* Return a {@code ResponseEntity} without a body. For an error response
|
||||||
* (status code of 4xx or 5xx), the {@code Mono} emits a
|
* (status code of 4xx or 5xx), the {@code Mono} emits a
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoSettings;
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
|
@ -459,6 +460,7 @@ public class DefaultWebClientTests {
|
||||||
testStatusHandlerForToEntity(spec.toEntityList(new ParameterizedTypeReference<String>() {}));
|
testStatusHandlerForToEntity(spec.toEntityList(new ParameterizedTypeReference<String>() {}));
|
||||||
testStatusHandlerForToEntity(spec.toEntityFlux(String.class));
|
testStatusHandlerForToEntity(spec.toEntityFlux(String.class));
|
||||||
testStatusHandlerForToEntity(spec.toEntityFlux(new ParameterizedTypeReference<String>() {}));
|
testStatusHandlerForToEntity(spec.toEntityFlux(new ParameterizedTypeReference<String>() {}));
|
||||||
|
testStatusHandlerForToEntity(spec.toEntityFlux(BodyExtractors.toFlux(String.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testStatusHandlerForToEntity(Publisher<?> responsePublisher) {
|
private void testStatusHandlerForToEntity(Publisher<?> responsePublisher) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.springframework.web.reactive.function.BodyExtractors;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.netty.http.client.HttpClient;
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
|
@ -342,6 +343,39 @@ class WebClientIntegrationTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedWebClientTest
|
||||||
|
void retrieveJsonArrayAsResponseEntityFluxWithBodyExtractor(ClientHttpConnector connector) {
|
||||||
|
startServer(connector);
|
||||||
|
|
||||||
|
String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]";
|
||||||
|
prepareResponse(response -> response
|
||||||
|
.setHeader("Content-Type", "application/json").setBody(content));
|
||||||
|
|
||||||
|
ResponseEntity<Flux<Pojo>> entity = this.webClient.get()
|
||||||
|
.uri("/json").accept(MediaType.APPLICATION_JSON)
|
||||||
|
.retrieve()
|
||||||
|
.toEntityFlux(BodyExtractors.toFlux(Pojo.class))
|
||||||
|
.block(Duration.ofSeconds(3));
|
||||||
|
|
||||||
|
assertThat(entity).isNotNull();
|
||||||
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(entity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
|
||||||
|
assertThat(entity.getHeaders().getContentLength()).isEqualTo(58);
|
||||||
|
|
||||||
|
assertThat(entity.getBody()).isNotNull();
|
||||||
|
StepVerifier.create(entity.getBody())
|
||||||
|
.expectNext(new Pojo("foo1", "bar1"))
|
||||||
|
.expectNext(new Pojo("foo2", "bar2"))
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(3));
|
||||||
|
|
||||||
|
expectRequestCount(1);
|
||||||
|
expectRequest(request -> {
|
||||||
|
assertThat(request.getPath()).isEqualTo("/json");
|
||||||
|
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo("application/json");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test // gh-24788
|
@Test // gh-24788
|
||||||
void retrieveJsonArrayAsBodilessEntityShouldReleasesConnection() {
|
void retrieveJsonArrayAsBodilessEntityShouldReleasesConnection() {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue