Fix SSE flushing in SseServerResponse

Prior to this commit, `SseServerResponse.send()` would flush the
output stream returned by `getBody()`. Since gh-36385,
`ServletServerHttpResponse` wraps this stream with a non-flushing
decorator to avoid performance issues with `HttpMessageConverter`
implementations that flush excessively. As a result, SSE events were
no longer flushed to the client.

This commit changes `send()` to call `outputMessage.flush()` instead
of `body.flush()`, which properly delegates to the servlet response
`flushBuffer()` and is not affected by the non-flushing wrapper.

Fixes gh-36537
This commit is contained in:
Brian Clozel
2026-03-25 21:45:24 +01:00
parent 6cea397bd8
commit 9140ed6fa9
2 changed files with 37 additions and 4 deletions
@@ -17,7 +17,6 @@
package org.springframework.web.servlet.function;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
@@ -137,9 +136,8 @@ final class SseServerResponse extends AbstractServerResponse {
public void send() throws IOException {
this.builder.append('\n');
try {
OutputStream body = this.outputMessage.getBody();
body.write(builderBytes());
body.flush();
this.outputMessage.getBody().write(builderBytes());
this.outputMessage.flush();
}
catch (IOException ex) {
this.sendFailed = true;
@@ -75,6 +75,41 @@ class SseServerResponseTests {
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
}
@Test // gh-36537
void sendStringFlushesResponse() throws Exception {
ServerResponse response = ServerResponse.sse(sse -> {
try {
sse.send("foo bar");
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});
ServerResponse.Context context = Collections::emptyList;
response.writeTo(this.mockRequest, this.mockResponse, context);
assertThat(this.mockResponse.isCommitted()).isTrue();
}
@Test // gh-36537
void sendObjectFlushesResponse() throws Exception {
Person person = new Person("John Doe", 42);
ServerResponse response = ServerResponse.sse(sse -> {
try {
sse.send(person);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});
ServerResponse.Context context = () -> List.of(new JacksonJsonHttpMessageConverter());
response.writeTo(this.mockRequest, this.mockResponse, context);
assertThat(this.mockResponse.isCommitted()).isTrue();
}
@Test
void sendObject() throws Exception {
Person person = new Person("John Doe", 42);