Browse Source

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
7.0.x
Brian Clozel 5 days ago
parent
commit
9140ed6fa9
  1. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java
  2. 35
      spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java

6
spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java

@ -17,7 +17,6 @@ @@ -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 { @@ -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;

35
spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java

@ -75,6 +75,41 @@ class SseServerResponseTests { @@ -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);

Loading…
Cancel
Save