Browse Source

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
pull/1448/merge
Arjen Poutsma 8 years ago
parent
commit
50fc8f4e32
  1. 13
      spring-webflux/src/main/java/org/springframework/web/reactive/function/BodyExtractors.java
  2. 11
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
  3. 33
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

13
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.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
@ -101,7 +102,8 @@ public abstract class BodyExtractors {
return reader.readMono(elementType, inputMessage, context.hints()); 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()); return reader.read(elementType, inputMessage, context.hints());
} }
}, },
Flux::error); Flux::error,
Flux::empty);
} }
/** /**
@ -221,8 +224,12 @@ public abstract class BodyExtractors {
private static <T, S extends Publisher<T>> S readWithMessageReaders( private static <T, S extends Publisher<T>> S readWithMessageReaders(
ReactiveHttpInputMessage inputMessage, BodyExtractor.Context context, ResolvableType elementType, ReactiveHttpInputMessage inputMessage, BodyExtractor.Context context, ResolvableType elementType,
Function<HttpMessageReader<T>, S> readerFunction, Function<Throwable, S> unsupportedError) { Function<HttpMessageReader<T>, S> readerFunction, Function<Throwable, S> unsupportedError,
Supplier<S> empty) {
if (elementType.equals(ResolvableType.forClass(Void.class))) {
return empty.get();
}
MediaType contentType = contentType(inputMessage); MediaType contentType = contentType(inputMessage);
List<HttpMessageReader<?>> messageReaders = context.messageReaders(); List<HttpMessageReader<?>> messageReaders = context.messageReaders();
return messageReaders.stream() return messageReaders.stream()

11
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

@ -387,11 +387,14 @@ class DefaultWebClient implements WebClient {
@Override @Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) { public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
return this.responseMono.flatMap(response -> return this.responseMono.flatMap(response -> {
response.bodyToMono(bodyType).map(body -> {
HttpHeaders headers = response.headers().asHttpHeaders(); 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))));
}
); );
} }

33
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

@ -52,12 +52,10 @@ public class WebClientIntegrationTests {
private WebClient webClient; private WebClient webClient;
private String baseUrl;
@Before @Before
public void setup() { public void setup() {
this.server = new MockWebServer(); this.server = new MockWebServer();
baseUrl = this.server.url("/").toString(); String baseUrl = this.server.url("/").toString();
this.webClient = WebClient.create(baseUrl); this.webClient = WebClient.create(baseUrl);
} }
@ -468,6 +466,35 @@ public class WebClientIntegrationTests {
Assert.assertEquals(2, server.getRequestCount()); Assert.assertEquals(2, server.getRequestCount());
} }
@Test
public void exchangeNoContent() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Length", "0"));
Mono<ClientResponse> 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<ResponseEntity<Void>> 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") @SuppressWarnings("serial")
private static class MyException extends RuntimeException { private static class MyException extends RuntimeException {

Loading…
Cancel
Save