Skip to content

Commit 280da61

Browse files
committed
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
1 parent 1dac0df commit 280da61

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* @author Arjen Poutsma
4949
* @author Sebastien Deleuze
5050
* @author Rossen Stoyanchev
51+
* @author Brian Clozel
5152
* @since 5.0
5253
* @param <T> the type of objects in the input stream
5354
*/
@@ -119,9 +120,10 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme
119120
HttpHeaders headers = message.getHeaders();
120121
if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
121122
return Mono.from(body)
122-
.flatMap(dataBuffer -> {
123-
headers.setContentLength(dataBuffer.readableByteCount());
124-
return message.writeWith(Mono.just(dataBuffer));
123+
.defaultIfEmpty(message.bufferFactory().wrap(new byte[0]))
124+
.flatMap(buffer -> {
125+
headers.setContentLength(buffer.readableByteCount());
126+
return message.writeWith(Mono.just(buffer));
125127
});
126128
}
127129
}

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

+38-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.http.codec;
1818

19+
import java.nio.charset.StandardCharsets;
1920
import java.util.Arrays;
2021
import java.util.Collections;
2122
import java.util.List;
@@ -28,8 +29,12 @@
2829
import org.mockito.MockitoAnnotations;
2930
import reactor.core.publisher.Flux;
3031
import reactor.core.publisher.Mono;
32+
import reactor.test.StepVerifier;
3133

3234
import org.springframework.core.codec.Encoder;
35+
import org.springframework.core.io.buffer.DataBuffer;
36+
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
37+
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
3338
import org.springframework.http.MediaType;
3439
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
3540
import org.springframework.util.MimeType;
@@ -50,6 +55,7 @@
5055
/**
5156
* Unit tests for {@link EncoderHttpMessageWriter}.
5257
* @author Rossen Stoyanchev
58+
* @author Brian Clozel
5359
*/
5460
public class EncoderHttpMessageWriterTests {
5561

@@ -75,13 +81,13 @@ public void setUp() throws Exception {
7581

7682

7783
@Test
78-
public void getWritableMediaTypes() throws Exception {
84+
public void getWritableMediaTypes() {
7985
HttpMessageWriter<?> writer = getWriter(MimeTypeUtils.TEXT_HTML, MimeTypeUtils.TEXT_XML);
8086
assertEquals(Arrays.asList(TEXT_HTML, TEXT_XML), writer.getWritableMediaTypes());
8187
}
8288

8389
@Test
84-
public void canWrite() throws Exception {
90+
public void canWrite() {
8591
HttpMessageWriter<?> writer = getWriter(MimeTypeUtils.TEXT_HTML);
8692
when(this.encoder.canEncode(forClass(String.class), TEXT_HTML)).thenReturn(true);
8793

@@ -90,7 +96,7 @@ public void canWrite() throws Exception {
9096
}
9197

9298
@Test
93-
public void useNegotiatedMediaType() throws Exception {
99+
public void useNegotiatedMediaType() {
94100
HttpMessageWriter<String> writer = getWriter(MimeTypeUtils.ALL);
95101
writer.write(Mono.just("body"), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS);
96102

@@ -99,7 +105,7 @@ public void useNegotiatedMediaType() throws Exception {
99105
}
100106

101107
@Test
102-
public void useDefaultMediaType() throws Exception {
108+
public void useDefaultMediaType() {
103109
testDefaultMediaType(null);
104110
testDefaultMediaType(new MediaType("text", "*"));
105111
testDefaultMediaType(new MediaType("*", "*"));
@@ -108,7 +114,6 @@ public void useDefaultMediaType() throws Exception {
108114

109115
private void testDefaultMediaType(MediaType negotiatedMediaType) {
110116

111-
this.response = new MockServerHttpResponse();
112117
this.mediaTypeCaptor = ArgumentCaptor.forClass(MediaType.class);
113118

114119
MimeType defaultContentType = MimeTypeUtils.TEXT_XML;
@@ -120,7 +125,7 @@ private void testDefaultMediaType(MediaType negotiatedMediaType) {
120125
}
121126

122127
@Test
123-
public void useDefaultMediaTypeCharset() throws Exception {
128+
public void useDefaultMediaTypeCharset() {
124129
HttpMessageWriter<String> writer = getWriter(TEXT_PLAIN_UTF_8, TEXT_HTML);
125130
writer.write(Mono.just("body"), forClass(String.class), TEXT_HTML, response, NO_HINTS);
126131

@@ -129,7 +134,7 @@ public void useDefaultMediaTypeCharset() throws Exception {
129134
}
130135

131136
@Test
132-
public void useNegotiatedMediaTypeCharset() throws Exception {
137+
public void useNegotiatedMediaTypeCharset() {
133138

134139
MediaType negotiatedMediaType = new MediaType("text", "html", ISO_8859_1);
135140

@@ -141,7 +146,7 @@ public void useNegotiatedMediaTypeCharset() throws Exception {
141146
}
142147

143148
@Test
144-
public void useHttpOutputMessageMediaType() throws Exception {
149+
public void useHttpOutputMessageMediaType() {
145150

146151
MediaType outputMessageMediaType = MediaType.TEXT_HTML;
147152
this.response.getHeaders().setContentType(outputMessageMediaType);
@@ -153,11 +158,35 @@ public void useHttpOutputMessageMediaType() throws Exception {
153158
assertEquals(outputMessageMediaType, this.mediaTypeCaptor.getValue());
154159
}
155160

161+
@Test
162+
public void setContentLengthForMonoBody() {
163+
164+
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
165+
DataBuffer buffer = factory.wrap("body".getBytes(StandardCharsets.UTF_8));
166+
HttpMessageWriter<String> writer = getWriter(Flux.just(buffer), MimeTypeUtils.TEXT_PLAIN);
167+
168+
writer.write(Mono.just("body"), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS).block();
169+
170+
assertEquals(4, this.response.getHeaders().getContentLength());
171+
}
172+
173+
@Test // SPR-17220
174+
public void emptyBodyWritten() {
175+
HttpMessageWriter<String> writer = getWriter(MimeTypeUtils.TEXT_PLAIN);
176+
writer.write(Mono.empty(), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS).block();
177+
StepVerifier.create(this.response.getBody()).expectNextCount(1).verifyComplete();
178+
assertEquals(0, this.response.getHeaders().getContentLength());
179+
}
180+
156181

157182
private HttpMessageWriter<String> getWriter(MimeType... mimeTypes) {
183+
return getWriter(Flux.empty(), mimeTypes);
184+
}
185+
186+
private HttpMessageWriter<String> getWriter(Flux<DataBuffer> encodedStream, MimeType... mimeTypes) {
158187
List<MimeType> typeList = Arrays.asList(mimeTypes);
159188
when(this.encoder.getEncodableMimeTypes()).thenReturn(typeList);
160-
when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(Flux.empty());
189+
when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(encodedStream);
161190
return new EncoderHttpMessageWriter<>(this.encoder);
162191
}
163192

0 commit comments

Comments
 (0)