From 50fc8f4e327078e512f7eb25538a36f7a58a1801 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 20 Jun 2017 16:30:37 +0200 Subject: [PATCH] Support Void response body type in WebClient This commit adds support for Void response body types in the WebClient, both when using `exchange` with a response.bodyToMono(Void.class), as well as using `retrieve` with `toEntity(Void.class)`. Issue: SPR-15679 --- .../web/reactive/function/BodyExtractors.java | 13 ++++++-- .../function/client/DefaultWebClient.java | 11 ++++--- .../client/WebClientIntegrationTests.java | 33 +++++++++++++++++-- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java index b08a657266..c09b7106d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function; import java.util.List; import java.util.Optional; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.reactivestreams.Publisher; @@ -101,7 +102,8 @@ public abstract class BodyExtractors { return reader.readMono(elementType, inputMessage, context.hints()); } }, - Mono::error); + Mono::error, + Mono::empty); } /** @@ -149,7 +151,8 @@ public abstract class BodyExtractors { return reader.read(elementType, inputMessage, context.hints()); } }, - Flux::error); + Flux::error, + Flux::empty); } /** @@ -221,8 +224,12 @@ public abstract class BodyExtractors { private static > S readWithMessageReaders( ReactiveHttpInputMessage inputMessage, BodyExtractor.Context context, ResolvableType elementType, - Function, S> readerFunction, Function unsupportedError) { + Function, S> readerFunction, Function unsupportedError, + Supplier empty) { + if (elementType.equals(ResolvableType.forClass(Void.class))) { + return empty.get(); + } MediaType contentType = contentType(inputMessage); List> messageReaders = context.messageReaders(); return messageReaders.stream() 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 2b44659191..ae7a802477 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 @@ -387,11 +387,14 @@ class DefaultWebClient implements WebClient { @Override public Mono> toEntity(Class bodyType) { - return this.responseMono.flatMap(response -> - response.bodyToMono(bodyType).map(body -> { + return this.responseMono.flatMap(response -> { HttpHeaders headers = response.headers().asHttpHeaders(); - return new ResponseEntity<>(body, headers, response.statusCode()); - }) + HttpStatus statusCode = response.statusCode(); + return response.bodyToMono(bodyType) + .map(body -> new ResponseEntity<>(body, headers, statusCode)) + .switchIfEmpty(Mono.defer( + () -> Mono.just(new ResponseEntity<>(headers, statusCode)))); + } ); } 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 85346e7255..162c8b3a54 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 @@ -52,12 +52,10 @@ public class WebClientIntegrationTests { private WebClient webClient; - private String baseUrl; - @Before public void setup() { this.server = new MockWebServer(); - baseUrl = this.server.url("/").toString(); + String baseUrl = this.server.url("/").toString(); this.webClient = WebClient.create(baseUrl); } @@ -468,6 +466,35 @@ public class WebClientIntegrationTests { Assert.assertEquals(2, server.getRequestCount()); } + @Test + public void exchangeNoContent() throws Exception { + this.server.enqueue(new MockResponse().setHeader("Content-Length", "0")); + + Mono result = this.webClient.get() + .uri("/noContent") + .exchange(); + + StepVerifier.create(result).assertNext(r -> { + assertTrue(r.statusCode().is2xxSuccessful()); + StepVerifier.create(r.bodyToMono(Void.class)).verifyComplete(); + }).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 {