From b3e274b3d0ba3e129a35268ea6de634c1bfa5c49 Mon Sep 17 00:00:00 2001 From: "Spindler, Justin" Date: Wed, 18 Nov 2020 10:21:20 -0500 Subject: [PATCH 1/2] Add ResponseSpec#toEntityFlux overload that accepts BodyExtractor --- .../function/client/DefaultWebClient.java | 8 +++++ .../reactive/function/client/WebClient.java | 12 ++++++- .../client/DefaultWebClientTests.java | 2 ++ .../client/WebClientIntegrationTests.java | 34 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index db8b511927c9..b68888d0e332 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -43,11 +43,13 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; 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.BodyInserters; import org.springframework.web.util.UriBuilder; @@ -599,6 +601,12 @@ public Mono>> toEntityFlux(ParameterizedTypeReference handlerEntityFlux(response, response.bodyToFlux(elementTypeRef))); } + @Override + public Mono>> toEntityFlux(BodyExtractor, ? super ClientHttpResponse> bodyExtractor) { + return this.responseMono.flatMap(response -> + handlerEntityFlux(response, response.body(bodyExtractor))); + } + @Override public Mono> toBodilessEntity() { return this.responseMono.flatMap(response -> diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index c548e0b48af0..d8d41938f0e1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -40,8 +40,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.http.codec.ClientCodecConfigurer; 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.BodyInserters; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -889,7 +891,7 @@ ResponseSpec onRawStatus(IntPredicate statusCodePredicate, Mono>> toEntityFlux(Class 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 the body element type * @return the {@code ResponseEntity} @@ -897,6 +899,14 @@ ResponseSpec onRawStatus(IntPredicate statusCodePredicate, */ Mono>> toEntityFlux(ParameterizedTypeReference elementTypeReference); + /** + * Variant of {@link #toEntityFlux(Class)} with a {@link BodyExtractor}. + * @param bodyExtractor the {@code BodyExtractor} that reads from the response + * @param the body element type + * @return the {@code ResponseEntity} + */ + Mono>> toEntityFlux(BodyExtractor, ? super ClientHttpResponse> bodyExtractor); + /** * Return a {@code ResponseEntity} without a body. For an error response * (status code of 4xx or 5xx), the {@code Mono} emits a diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index ebc6f823001a..49c18860b23c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -31,6 +31,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.reactivestreams.Publisher; +import org.springframework.web.reactive.function.BodyExtractors; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -459,6 +460,7 @@ public void onStatusHandlersApplyForToEntityMethods() { testStatusHandlerForToEntity(spec.toEntityList(new ParameterizedTypeReference() {})); testStatusHandlerForToEntity(spec.toEntityFlux(String.class)); testStatusHandlerForToEntity(spec.toEntityFlux(new ParameterizedTypeReference() {})); + testStatusHandlerForToEntity(spec.toEntityFlux(BodyExtractors.toFlux(String.class))); } private void testStatusHandlerForToEntity(Publisher responsePublisher) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java index 4a940669d073..e25269d94619 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.web.reactive.function.BodyExtractors; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; @@ -342,6 +343,39 @@ void retrieveJsonArrayAsResponseEntityFlux(ClientHttpConnector connector) { }); } + @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> 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 void retrieveJsonArrayAsBodilessEntityShouldReleasesConnection() { From cb0038b2790d6e573d05137df3016e6c33bb22c9 Mon Sep 17 00:00:00 2001 From: "Spindler, Justin" Date: Wed, 18 Nov 2020 10:50:57 -0500 Subject: [PATCH 2/2] Add @since to javadoc --- .../springframework/web/reactive/function/client/WebClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index d8d41938f0e1..2a14a4b62ece 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -904,6 +904,7 @@ ResponseSpec onRawStatus(IntPredicate statusCodePredicate, * @param bodyExtractor the {@code BodyExtractor} that reads from the response * @param the body element type * @return the {@code ResponseEntity} + * @since 5.3.2 */ Mono>> toEntityFlux(BodyExtractor, ? super ClientHttpResponse> bodyExtractor);