Centralize Server Sent Event utility methods

Prior to this commit, many classes would support writing Server Sent
Events in some way to the response output stream. This has lead to some
code duplication.

This commit refactors the duplicated code in a shared `SseUtils` class.

Closes gh-37065
This commit is contained in:
Brian Clozel
2026-08-14 09:19:58 +02:00
parent 999f428987
commit 5abe6d3e5f
9 changed files with 230 additions and 146 deletions
@@ -64,6 +64,7 @@ import org.springframework.web.reactive.result.HandlerResultHandlerSupport;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.SseUtils;
/**
* {@code HandlerResultHandler} that encapsulates the view resolution algorithm
@@ -603,8 +604,9 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp
finally {
DataBufferUtils.release(buffer);
}
text = escapeSseFragment(text);
return bufferFactory.wrap(text.getBytes(charset));
StringBuilder escaped = new StringBuilder();
SseUtils.appendFieldValue("data", text, escaped);
return bufferFactory.wrap(escaped.toString().getBytes(charset));
});
return Flux.concat(Flux.just(prefix), content, Flux.just(suffix));
@@ -615,29 +617,6 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp
return bufferFactory.wrap(bytes);
}
private String escapeSseFragment(String content) {
if (content.indexOf('\n') == -1 && content.indexOf('\r') == -1) {
return content;
}
StringBuilder fragment = new StringBuilder();
int length = content.length();
for (int i = 0; i < length; i++) {
char c = content.charAt(i);
if (c == '\r') {
if (i + 1 < length && content.charAt(i + 1) == '\n') {
i++;
}
fragment.append("\ndata:");
}
else if (c == '\n') {
fragment.append("\ndata:");
}
else {
fragment.append(c);
}
}
return fragment.toString();
}
}
}