diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 03aab3dcb6c..c174b5842c6 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -119,7 +119,10 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) - .defaultIfEmpty(message.bufferFactory().wrap(new byte[0])) + .switchIfEmpty(Mono.defer(() -> { + headers.setContentLength(0); + return message.setComplete().then(Mono.empty()); + })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); diff --git a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java index 367712cf3e0..941d3cd7a11 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java @@ -34,23 +34,17 @@ import reactor.test.StepVerifier; import org.springframework.core.codec.Encoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static java.nio.charset.StandardCharsets.*; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.springframework.core.ResolvableType.forClass; -import static org.springframework.http.MediaType.TEXT_HTML; -import static org.springframework.http.MediaType.TEXT_PLAIN; -import static org.springframework.http.MediaType.TEXT_XML; +import static org.mockito.Mockito.*; +import static org.springframework.core.ResolvableType.*; +import static org.springframework.http.MediaType.*; /** * Unit tests for {@link EncoderHttpMessageWriter}. @@ -174,7 +168,7 @@ public class EncoderHttpMessageWriterTests { public void emptyBodyWritten() { HttpMessageWriter writer = getWriter(MimeTypeUtils.TEXT_PLAIN); writer.write(Mono.empty(), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS).block(); - StepVerifier.create(this.response.getBody()).expectNextCount(1).verifyComplete(); + StepVerifier.create(this.response.getBody()).expectComplete(); assertEquals(0, this.response.getHeaders().getContentLength()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java index cc94301f71f..b140789dbc9 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingMessageConversionIntegrationTests.java @@ -149,6 +149,19 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq assertEquals(expected, responseEntity.getBody()); } + @Test // SPR-17506 + public void personResponseBodyWithEmptyMono() throws Exception { + ResponseEntity responseEntity = performGet("/person-response/mono-empty", JSON, Person.class); + assertEquals(0, responseEntity.getHeaders().getContentLength()); + assertNull(responseEntity.getBody()); + + // As we're on the same connection, the 2nd request proves server response handling + // did complete after the 1st request.. + responseEntity = performGet("/person-response/mono-empty", JSON, Person.class); + assertEquals(0, responseEntity.getHeaders().getContentLength()); + assertNull(responseEntity.getBody()); + } + @Test public void personResponseBodyWithMonoDeclaredAsObject() throws Exception { Person expected = new Person("Robert"); @@ -495,6 +508,11 @@ public class RequestMappingMessageConversionIntegrationTests extends AbstractReq return Mono.just(new Person("Robert")); } + @GetMapping("/mono-empty") + public Mono getMonoEmpty() { + return Mono.empty(); + } + @GetMapping("/mono-declared-as-object") public Object getMonoDeclaredAsObject() { return Mono.just(new Person("Robert"));