From c9a3b863c4a30fb080ad98ac0f2494f70658101f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 11 Jul 2017 17:40:16 +0200 Subject: [PATCH] Move toEntity(List) from WebClient.ResponseSpec to ClientResponse This commit moves `toEntity(Class)` and `toEntityList(Class)` from WebClient.ResponseSpec to ClientResponse. The main reason for doing so is that the newly introduced `onStatus` method (see 2f9bd6e075facbf13edd629a98da88115c130b98) does not apply to these two methods, and the result would be confusing. Also, `ClientResponse` and `ResponseEntity` represent the same data: status code, headers, and a body. Issue: SPR-15724 --- .../function/client/ClientResponse.java | 17 ++++++++++ .../client/DefaultClientResponse.java | 20 +++++++++++ .../function/client/DefaultWebClient.java | 24 -------------- .../reactive/function/client/WebClient.java | 21 ------------ .../client/ClientResponseExtensions.kt | 17 ++++++++++ .../function/client/WebClientExtensions.kt | 17 ---------- .../client/WebClientIntegrationTests.java | 33 +++++-------------- .../client/ClientResponseExtensionsTests.kt | 15 ++++++++- .../client/WebClientExtensionsTests.kt | 15 ++------- 9 files changed, 79 insertions(+), 100 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java index d8211f47af..6a6b3f7640 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientResponse.java @@ -27,6 +27,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyExtractor; @@ -82,6 +83,22 @@ public interface ClientResponse { */ Flux bodyToFlux(Class elementClass); + /** + * Return this response as a delayed {@code ResponseEntity}. + * @param bodyType the expected response body type + * @param response body type + * @return {@code Mono} with the {@code ResponseEntity} + */ + Mono> toEntity(Class bodyType); + + /** + * Return this response as a delayed list of {@code ResponseEntity}s. + * @param elementType the expected response body list element type + * @param the type of elements in the list + * @return {@code Mono} with the list of {@code ResponseEntity}s + */ + Mono>> toEntityList(Class elementType); + /** * Represents the headers of the HTTP response. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java index 01c5417158..3d8fb9b95f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponse.java @@ -29,6 +29,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -103,6 +104,25 @@ class DefaultClientResponse implements ClientResponse { return body(BodyExtractors.toFlux(elementClass)); } + @Override + public Mono> toEntity(Class bodyType) { + HttpHeaders headers = headers().asHttpHeaders(); + HttpStatus statusCode = statusCode(); + return bodyToMono(bodyType) + .map(body -> new ResponseEntity<>(body, headers, statusCode)) + .switchIfEmpty(Mono.defer( + () -> Mono.just(new ResponseEntity<>(headers, statusCode)))); + } + + @Override + public Mono>> toEntityList(Class responseType) { + HttpHeaders headers = headers().asHttpHeaders(); + HttpStatus statusCode = statusCode(); + return bodyToFlux(responseType) + .collectList() + .map(body -> new ResponseEntity<>(body, headers, statusCode)); + } + private class DefaultHeaders implements Headers { 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 c39217addd..1dc3551b38 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 @@ -39,7 +39,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; 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; @@ -443,28 +442,5 @@ class DefaultWebClient implements WebClient { .orElse(response.body(extractor)); } - - @Override - public Mono> toEntity(Class bodyType) { - return this.responseMono.flatMap(response -> { - HttpHeaders headers = response.headers().asHttpHeaders(); - HttpStatus statusCode = response.statusCode(); - return response.bodyToMono(bodyType) - .map(body -> new ResponseEntity<>(body, headers, statusCode)) - .switchIfEmpty(Mono.defer( - () -> Mono.just(new ResponseEntity<>(headers, statusCode)))); - } - ); - } - - @Override - public Mono>> toEntityList(Class responseType) { - return this.responseMono.flatMap(response -> - response.bodyToFlux(responseType).collectList().map(body -> { - HttpHeaders headers = response.headers().asHttpHeaders(); - return new ResponseEntity<>(body, headers, response.statusCode()); - }) - ); - } } } 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 d092a4fe0e..ca21357dae 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 @@ -33,7 +33,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.util.MultiValueMap; @@ -573,26 +572,6 @@ public interface WebClient { */ Flux bodyToFlux(Class elementType); - /** - * Returns the response as a delayed {@code ResponseEntity}. Unlike - * {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}, this method does not check - * for a 4xx or 5xx status code before extracting the body. - * @param bodyType the expected response body type - * @param response body type - * @return {@code Mono} with the {@code ResponseEntity} - */ - Mono> toEntity(Class bodyType); - - /** - * Returns the response as a delayed list of {@code ResponseEntity}s. Unlike - * {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}, this method does not check - * for a 4xx or 5xx status code before extracting the body. - * @param elementType the expected response body list element type - * @param the type of elements in the list - * @return {@code Mono} with the list of {@code ResponseEntity}s - */ - Mono>> toEntityList(Class elementType); - } diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensions.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensions.kt index 33ef072692..a0c939ac5f 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensions.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensions.kt @@ -16,6 +16,7 @@ package org.springframework.web.reactive.function.client +import org.springframework.http.ResponseEntity import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -35,3 +36,19 @@ inline fun ClientResponse.bodyToMono(): Mono = bodyToMono(T * @since 5.0 */ inline fun ClientResponse.bodyToFlux(): Flux = bodyToFlux(T::class.java) + +/** + * Extension for [ClientResponse.toEntity] providing a `toEntity()` variant. + * + * @author Sebastien Deleuze + * @since 5.0 + */ +inline fun ClientResponse.toEntity(): Mono> = toEntity(T::class.java) + +/** + * Extension for [ClientResponse.toEntityList] providing a `bodyToEntityList()` variant. + * + * @author Sebastien Deleuze + * @since 5.0 + */ +inline fun ClientResponse.toEntityList(): Mono>> = toEntityList(T::class.java) diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt index 462a9b9d71..ff23d7f946 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt +++ b/spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt @@ -17,7 +17,6 @@ package org.springframework.web.reactive.function.client import org.reactivestreams.Publisher -import org.springframework.http.ResponseEntity import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -49,19 +48,3 @@ inline fun WebClient.ResponseSpec.bodyToMono(): Mono = body * @since 5.0 */ inline fun WebClient.ResponseSpec.bodyToFlux(): Flux = bodyToFlux(T::class.java) - -/** - * Extension for [WebClient.ResponseSpec.toEntity] providing a `bodyToEntity()` variant. - * - * @author Sebastien Deleuze - * @since 5.0 - */ -inline fun WebClient.ResponseSpec.toEntity(): Mono> = toEntity(T::class.java) - -/** - * Extension for [WebClient.ResponseSpec.toEntityList] providing a `bodyToEntityList()` variant. - * - * @author Sebastien Deleuze - * @since 5.0 - */ -inline fun WebClient.ResponseSpec.toEntityList(): Mono>> = toEntityList(T::class.java) 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 7f93b6f2c3..ebe485e978 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 @@ -158,7 +158,7 @@ public class WebClientIntegrationTests { } @Test - public void jsonStringRetrieveEntity() throws Exception { + public void jsonStringExchangeEntity() throws Exception { String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"; this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json") .setBody(content)); @@ -166,8 +166,8 @@ public class WebClientIntegrationTests { Mono> result = this.webClient.get() .uri("/json") .accept(MediaType.APPLICATION_JSON) - .retrieve() - .toEntity(String.class); + .exchange() + .flatMap(response -> response.toEntity(String.class)); StepVerifier.create(result) .consumeNextWith(entity -> { @@ -186,15 +186,15 @@ public class WebClientIntegrationTests { } @Test - public void jsonStringRetrieveEntityList() throws Exception { + public void jsonStringExchangeEntityList() throws Exception { String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]"; this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json").setBody(content)); Mono>> result = this.webClient.get() .uri("/json") .accept(MediaType.APPLICATION_JSON) - .retrieve() - .toEntityList(Pojo.class); + .exchange() + .flatMap(response -> response.toEntityList(Pojo.class)); StepVerifier.create(result) .consumeNextWith(entity -> { @@ -412,14 +412,14 @@ public class WebClientIntegrationTests { } @Test - public void retrieveToEntityNotFound() throws Exception { + public void exchangeToEntityNotFound() throws Exception { this.server.enqueue(new MockResponse().setResponseCode(404) .setHeader("Content-Type", "text/plain").setBody("Not Found")); Mono> result = this.webClient.get() .uri("/greeting?name=Spring") - .retrieve() - .toEntity(String.class); + .exchange() + .flatMap(response -> response.toEntity(String.class)); StepVerifier.create(result) .consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode())) @@ -521,21 +521,6 @@ public class WebClientIntegrationTests { }).verifyComplete(); } - @Test - public void retrieveNoContent() throws Exception { - this.server.enqueue(new MockResponse().setHeader("Content-Length", "0")); - - Mono> result = this.webClient.get() - .uri("/noContent") - .retrieve() - .toEntity(Void.class); - - StepVerifier.create(result).assertNext(r -> { - assertFalse(r.hasBody()); - assertTrue(r.getStatusCode().is2xxSuccessful()); - }).verifyComplete(); - } - @SuppressWarnings("serial") private static class MyException extends RuntimeException { diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensionsTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensionsTests.kt index 0fc8986424..ee6f03d2c8 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensionsTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensionsTests.kt @@ -20,7 +20,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner /** @@ -46,5 +47,17 @@ class ClientResponseExtensionsTests { verify(response, times(1)).bodyToFlux(Foo::class.java) } + @Test + fun `toEntity with reified type parameters`() { + response.toEntity() + verify(response, times(1)).toEntity(Foo::class.java) + } + + @Test + fun `ResponseSpec#toEntityList with reified type parameters`() { + response.toEntityList() + verify(response, times(1)).toEntityList(Foo::class.java) + } + class Foo } diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt index 8a150d6f63..e3f3b93f1a 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt @@ -21,7 +21,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnitRunner import org.reactivestreams.Publisher @@ -59,17 +60,5 @@ class WebClientExtensionsTests { verify(responseSpec, times(1)).bodyToFlux(Foo::class.java) } - @Test - fun `ResponseSpec#toEntity with reified type parameters`() { - responseSpec.toEntity() - verify(responseSpec, times(1)).toEntity(Foo::class.java) - } - - @Test - fun `ResponseSpec#toEntityList with reified type parameters`() { - responseSpec.toEntityList() - verify(responseSpec, times(1)).toEntityList(Foo::class.java) - } - class Foo }