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 b7ca0438a9..3d97a948dd 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 @@ -48,6 +48,7 @@ import org.springframework.util.Assert; * @author Arjen Poutsma * @author Sebastien Deleuze * @author Rossen Stoyanchev + * @author Brian Clozel * @since 5.0 * @param the type of objects in the input stream */ @@ -119,9 +120,10 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { HttpHeaders headers = message.getHeaders(); if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { return Mono.from(body) - .flatMap(dataBuffer -> { - headers.setContentLength(dataBuffer.readableByteCount()); - return message.writeWith(Mono.just(dataBuffer)); + .defaultIfEmpty(message.bufferFactory().wrap(new byte[0])) + .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 43922bee02..367712cf3e 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 @@ -16,6 +16,7 @@ package org.springframework.http.codec; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -28,8 +29,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +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; @@ -50,6 +55,7 @@ import static org.springframework.http.MediaType.TEXT_XML; /** * Unit tests for {@link EncoderHttpMessageWriter}. * @author Rossen Stoyanchev + * @author Brian Clozel */ public class EncoderHttpMessageWriterTests { @@ -75,13 +81,13 @@ public class EncoderHttpMessageWriterTests { @Test - public void getWritableMediaTypes() throws Exception { + public void getWritableMediaTypes() { HttpMessageWriter writer = getWriter(MimeTypeUtils.TEXT_HTML, MimeTypeUtils.TEXT_XML); assertEquals(Arrays.asList(TEXT_HTML, TEXT_XML), writer.getWritableMediaTypes()); } @Test - public void canWrite() throws Exception { + public void canWrite() { HttpMessageWriter writer = getWriter(MimeTypeUtils.TEXT_HTML); when(this.encoder.canEncode(forClass(String.class), TEXT_HTML)).thenReturn(true); @@ -90,7 +96,7 @@ public class EncoderHttpMessageWriterTests { } @Test - public void useNegotiatedMediaType() throws Exception { + public void useNegotiatedMediaType() { HttpMessageWriter writer = getWriter(MimeTypeUtils.ALL); writer.write(Mono.just("body"), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS); @@ -99,7 +105,7 @@ public class EncoderHttpMessageWriterTests { } @Test - public void useDefaultMediaType() throws Exception { + public void useDefaultMediaType() { testDefaultMediaType(null); testDefaultMediaType(new MediaType("text", "*")); testDefaultMediaType(new MediaType("*", "*")); @@ -108,7 +114,6 @@ public class EncoderHttpMessageWriterTests { private void testDefaultMediaType(MediaType negotiatedMediaType) { - this.response = new MockServerHttpResponse(); this.mediaTypeCaptor = ArgumentCaptor.forClass(MediaType.class); MimeType defaultContentType = MimeTypeUtils.TEXT_XML; @@ -120,7 +125,7 @@ public class EncoderHttpMessageWriterTests { } @Test - public void useDefaultMediaTypeCharset() throws Exception { + public void useDefaultMediaTypeCharset() { HttpMessageWriter writer = getWriter(TEXT_PLAIN_UTF_8, TEXT_HTML); writer.write(Mono.just("body"), forClass(String.class), TEXT_HTML, response, NO_HINTS); @@ -129,7 +134,7 @@ public class EncoderHttpMessageWriterTests { } @Test - public void useNegotiatedMediaTypeCharset() throws Exception { + public void useNegotiatedMediaTypeCharset() { MediaType negotiatedMediaType = new MediaType("text", "html", ISO_8859_1); @@ -141,7 +146,7 @@ public class EncoderHttpMessageWriterTests { } @Test - public void useHttpOutputMessageMediaType() throws Exception { + public void useHttpOutputMessageMediaType() { MediaType outputMessageMediaType = MediaType.TEXT_HTML; this.response.getHeaders().setContentType(outputMessageMediaType); @@ -153,11 +158,35 @@ public class EncoderHttpMessageWriterTests { assertEquals(outputMessageMediaType, this.mediaTypeCaptor.getValue()); } + @Test + public void setContentLengthForMonoBody() { + + DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + DataBuffer buffer = factory.wrap("body".getBytes(StandardCharsets.UTF_8)); + HttpMessageWriter writer = getWriter(Flux.just(buffer), MimeTypeUtils.TEXT_PLAIN); + + writer.write(Mono.just("body"), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS).block(); + + assertEquals(4, this.response.getHeaders().getContentLength()); + } + + @Test // SPR-17220 + 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(); + assertEquals(0, this.response.getHeaders().getContentLength()); + } + private HttpMessageWriter getWriter(MimeType... mimeTypes) { + return getWriter(Flux.empty(), mimeTypes); + } + + private HttpMessageWriter getWriter(Flux encodedStream, MimeType... mimeTypes) { List typeList = Arrays.asList(mimeTypes); when(this.encoder.getEncodableMimeTypes()).thenReturn(typeList); - when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(Flux.empty()); + when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(encodedStream); return new EncoderHttpMessageWriter<>(this.encoder); }