Browse Source

Fix empty body writing in EncoderHttpMessageWriter

Prior to this commit, an bug introduced in SPR-16949 prevented
`Mono.empty` bodies from being written to the response.

This commit ensures that empty bodies still trigger the writing to the
response and does not hang the processing of the exchange.

Issue: SPR-17220
pull/1948/head
Brian Clozel 6 years ago
parent
commit
280da61d5c
  1. 8
      spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java
  2. 47
      spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java

8
spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java

@ -48,6 +48,7 @@ import org.springframework.util.Assert; @@ -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 <T> the type of objects in the input stream
*/
@ -119,9 +120,10 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> { @@ -119,9 +120,10 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
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));
});
}
}

47
spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTests.java

@ -16,6 +16,7 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -90,7 +96,7 @@ public class EncoderHttpMessageWriterTests {
}
@Test
public void useNegotiatedMediaType() throws Exception {
public void useNegotiatedMediaType() {
HttpMessageWriter<String> 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 { @@ -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 { @@ -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 { @@ -120,7 +125,7 @@ public class EncoderHttpMessageWriterTests {
}
@Test
public void useDefaultMediaTypeCharset() throws Exception {
public void useDefaultMediaTypeCharset() {
HttpMessageWriter<String> 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 { @@ -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 { @@ -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 { @@ -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<String> 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<String> 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<String> getWriter(MimeType... mimeTypes) {
return getWriter(Flux.empty(), mimeTypes);
}
private HttpMessageWriter<String> getWriter(Flux<DataBuffer> encodedStream, MimeType... mimeTypes) {
List<MimeType> 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);
}

Loading…
Cancel
Save