mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Enforce "data: " prefix for outgoing SSE data payloads
Prior to this commit, SSE support in Spring would write payloads with the "data:" prefix (without space). While this is OK with the standard, this makes it harder for implementations to support reading and writing payloads with Spring (the round trip use case). This commit introduces a breaking change and now enforces "data: " in all variants. This has the potential of breaking some low level test suites with text/plain or custom media types, but this should overall make the situation better for developers. Closes gh-37242
This commit is contained in:
+1
-1
@@ -52,7 +52,7 @@ class ReactiveReturnTypeTests {
|
|||||||
.andReturn();
|
.andReturn();
|
||||||
|
|
||||||
mockMvc.perform(asyncDispatch(mvcResult))
|
mockMvc.perform(asyncDispatch(mvcResult))
|
||||||
.andExpect(content().string("data:event0\n\ndata:event1\n\ndata:event2\n\n"));
|
.andExpect(content().string("data: event0\n\ndata: event1\n\ndata: event2\n\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public final class ServerSentEvent<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a StringBuilder with the id, event, retry, and comment fields fully
|
* Return a StringBuilder with the id, event, retry, and comment fields fully
|
||||||
* serialized, and also appending "data:" if there is data.
|
* serialized, and also appending "data: " if there is data.
|
||||||
* @since 6.2.1
|
* @since 6.2.1
|
||||||
*/
|
*/
|
||||||
public String format() {
|
public String format() {
|
||||||
@@ -116,7 +116,7 @@ public final class ServerSentEvent<T> {
|
|||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
if (this.data != null) {
|
if (this.data != null) {
|
||||||
sb.append("data:");
|
sb.append("data: ");
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ public class Jackson2JsonEncoder extends AbstractJackson2Encoder {
|
|||||||
|
|
||||||
private static PrettyPrinter initSsePrettyPrinter() {
|
private static PrettyPrinter initSsePrettyPrinter() {
|
||||||
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
|
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
|
||||||
printer.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
printer.indentObjectsWith(new DefaultIndenter(" ", "\ndata: "));
|
||||||
return printer;
|
return printer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ public class JacksonJsonEncoder extends AbstractJacksonEncoder<JsonMapper> {
|
|||||||
|
|
||||||
private static PrettyPrinter initSsePrettyPrinter() {
|
private static PrettyPrinter initSsePrettyPrinter() {
|
||||||
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
|
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
|
||||||
printer.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
printer.indentObjectsWith(new DefaultIndenter(" ", "\ndata: "));
|
||||||
return printer;
|
return printer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ public abstract class AbstractJacksonHttpMessageConverter<T extends ObjectMapper
|
|||||||
|
|
||||||
private PrettyPrinter initSsePrettyPrinter() {
|
private PrettyPrinter initSsePrettyPrinter() {
|
||||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||||
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata: "));
|
||||||
return prettyPrinter;
|
return prettyPrinter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
|||||||
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
|
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
|
||||||
this.defaultObjectMapper = objectMapper;
|
this.defaultObjectMapper = objectMapper;
|
||||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||||
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata: "));
|
||||||
this.ssePrettyPrinter = prettyPrinter;
|
this.ssePrettyPrinter = prettyPrinter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ public abstract class SseUtils {
|
|||||||
/**
|
/**
|
||||||
* Append {@code value} to {@code output}, replacing each line separator
|
* Append {@code value} to {@code output}, replacing each line separator
|
||||||
* ({@code "\n"}, {@code "\r"}, or {@code "\r\n"}) it contains with a new
|
* ({@code "\n"}, {@code "\r"}, or {@code "\r\n"}) it contains with a new
|
||||||
* {@code field} line (that is, {@code "\n" + field + ":"}). This keeps a
|
* {@code field} line (that is, {@code "\n" + field + ": "}, or
|
||||||
* multi-line field value from breaking out of the current SSE field when
|
* {@code "\n:"} for a comment). This keeps a multi-line field value from
|
||||||
* written on the wire.
|
* breaking out of the current SSE field when written on the wire.
|
||||||
* @param field the name of the SSE field that {@code value} belongs to
|
* @param field the name of the SSE field that {@code value} belongs to
|
||||||
* (for example, {@code "data"}), or an empty string for a comment
|
* (for example, {@code "data"}), or an empty string for a comment
|
||||||
* @param value the field value to escape and append
|
* @param value the field value to escape and append
|
||||||
@@ -44,7 +44,7 @@ public abstract class SseUtils {
|
|||||||
output.append(value);
|
output.append(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String lineSeparatorReplacement = "\n" + field + ":";
|
String lineSeparatorReplacement = "\n" + field + (field.isEmpty() ? ":" : ": ");
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
char c = value.charAt(i);
|
char c = value.charAt(i);
|
||||||
|
|||||||
+18
-18
@@ -85,7 +85,7 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
|
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer(
|
.consumeNextWith(stringConsumer(
|
||||||
"id:c42\nevent:foo\nretry:123\n:bla\n:bla bla\n:bla bla bla\ndata:bar\n\n"))
|
"id:c42\nevent:foo\nretry:123\n:bla\n:bla bla\n:bla bla bla\ndata: bar\n\n"))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,8 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
testWrite(source, outputMessage, String.class);
|
testWrite(source, outputMessage, String.class);
|
||||||
|
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer("data:foo\n\n"))
|
.consumeNextWith(stringConsumer("data: foo\n\n"))
|
||||||
.consumeNextWith(stringConsumer("data:bar\n\n"))
|
.consumeNextWith(stringConsumer("data: bar\n\n"))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
@@ -114,9 +114,9 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
testWrite(source, outputMessage, String.class);
|
testWrite(source, outputMessage, String.class);
|
||||||
|
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
|
.consumeNextWith(stringConsumer("data: first\ndata: second\n\n"))
|
||||||
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
|
.consumeNextWith(stringConsumer("data: first\ndata: second\n\n"))
|
||||||
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
|
.consumeNextWith(stringConsumer("data: first\ndata: second\n\n"))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
.consumeNextWith(dataBuffer -> {
|
.consumeNextWith(dataBuffer -> {
|
||||||
String value = dataBuffer.toString(charset);
|
String value = dataBuffer.toString(charset);
|
||||||
DataBufferUtils.release(dataBuffer);
|
DataBufferUtils.release(dataBuffer);
|
||||||
assertThat(value).isEqualTo("data:\u00A3\n\n");
|
assertThat(value).isEqualTo("data: \u00A3\n\n");
|
||||||
})
|
})
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
@@ -151,10 +151,10 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
testWrite(source, outputMessage, Pojo.class);
|
testWrite(source, outputMessage, Pojo.class);
|
||||||
|
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer("data:"))
|
.consumeNextWith(stringConsumer("data: "))
|
||||||
.consumeNextWith(stringConsumer("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"))
|
.consumeNextWith(stringConsumer("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"))
|
||||||
.consumeNextWith(stringConsumer("\n\n"))
|
.consumeNextWith(stringConsumer("\n\n"))
|
||||||
.consumeNextWith(stringConsumer("data:"))
|
.consumeNextWith(stringConsumer("data: "))
|
||||||
.consumeNextWith(stringConsumer("{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}"))
|
.consumeNextWith(stringConsumer("{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}"))
|
||||||
.consumeNextWith(stringConsumer("\n\n"))
|
.consumeNextWith(stringConsumer("\n\n"))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
@@ -173,19 +173,19 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
testWrite(source, outputMessage, Pojo.class);
|
testWrite(source, outputMessage, Pojo.class);
|
||||||
|
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer("data:"))
|
.consumeNextWith(stringConsumer("data: "))
|
||||||
.consumeNextWith(stringConsumer("""
|
.consumeNextWith(stringConsumer("""
|
||||||
{
|
{
|
||||||
data: "foo" : "foofoo",
|
data: "foo" : "foofoo",
|
||||||
data: "bar" : "barbar"
|
data: "bar" : "barbar"
|
||||||
data:}"""))
|
data: }"""))
|
||||||
.consumeNextWith(stringConsumer("\n\n"))
|
.consumeNextWith(stringConsumer("\n\n"))
|
||||||
.consumeNextWith(stringConsumer("data:"))
|
.consumeNextWith(stringConsumer("data: "))
|
||||||
.consumeNextWith(stringConsumer("""
|
.consumeNextWith(stringConsumer("""
|
||||||
{
|
{
|
||||||
data: "foo" : "foofoofoo",
|
data: "foo" : "foofoofoo",
|
||||||
data: "bar" : "barbarbar"
|
data: "bar" : "barbarbar"
|
||||||
data:}"""))
|
data: }"""))
|
||||||
.consumeNextWith(stringConsumer("\n\n"))
|
.consumeNextWith(stringConsumer("\n\n"))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify();
|
.verify();
|
||||||
@@ -203,7 +203,7 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
|
|||||||
|
|
||||||
assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(mediaType);
|
assertThat(outputMessage.getHeaders().getContentType()).isEqualTo(mediaType);
|
||||||
StepVerifier.create(outputMessage.getBody())
|
StepVerifier.create(outputMessage.getBody())
|
||||||
.consumeNextWith(stringConsumer("data:", charset))
|
.consumeNextWith(stringConsumer("data: ", charset))
|
||||||
.consumeNextWith(stringConsumer("{\"foo\":\"foo\uD834\uDD1E\",\"bar\":\"bar\uD834\uDD1E\"}", charset))
|
.consumeNextWith(stringConsumer("{\"foo\":\"foo\uD834\uDD1E\",\"bar\":\"bar\uD834\uDD1E\"}", charset))
|
||||||
.consumeNextWith(stringConsumer("\n\n", charset))
|
.consumeNextWith(stringConsumer("\n\n", charset))
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class ServerSentEventTests {
|
|||||||
void supportMultiLineComments(String newLine, String description) {
|
void supportMultiLineComments(String newLine, String description) {
|
||||||
ServerSentEvent<String> event = ServerSentEvent.<String>builder()
|
ServerSentEvent<String> event = ServerSentEvent.<String>builder()
|
||||||
.comment("foo" + newLine + "bar" + newLine + "baz").data("payload").build();
|
.comment("foo" + newLine + "bar" + newLine + "baz").data("payload").build();
|
||||||
assertThat(event.format()).isEqualTo(":foo\n:bar\n:baz\ndata:");
|
assertThat(event.format()).isEqualTo(":foo\n:bar\n:baz\ndata: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> newLineCharacters() {
|
private static Stream<Arguments> newLineCharacters() {
|
||||||
|
|||||||
+1
-1
@@ -415,7 +415,7 @@ class JacksonJsonHttpMessageConverterTests {
|
|||||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata:}");
|
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata: }");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+1
-1
@@ -385,7 +385,7 @@ class MappingJackson2HttpMessageConverterTests {
|
|||||||
this.converter.writeInternal(bean, null, outputMessage);
|
this.converter.writeInternal(bean, null, outputMessage);
|
||||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata:}");
|
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata: }");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ class SseUtilsTests {
|
|||||||
@MethodSource("newLineCharacters")
|
@MethodSource("newLineCharacters")
|
||||||
void appendFieldValueReplacesLineSeparatorWithFieldPrefix(String newLine, String description) {
|
void appendFieldValueReplacesLineSeparatorWithFieldPrefix(String newLine, String description) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("data:");
|
sb.append("data: ");
|
||||||
SseUtils.appendFieldValue("data", "first" + newLine + "second", sb);
|
SseUtils.appendFieldValue("data", "first" + newLine + "second", sb);
|
||||||
assertThat(sb).hasToString("data:first\ndata:second");
|
assertThat(sb).hasToString("data: first\ndata: second");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest(name = "{1}")
|
@ParameterizedTest(name = "{1}")
|
||||||
|
|||||||
+1
-1
@@ -590,7 +590,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp
|
|||||||
|
|
||||||
ServerSentEvent<?> sse = (ServerSentEvent<?>) hints;
|
ServerSentEvent<?> sse = (ServerSentEvent<?>) hints;
|
||||||
CharSequence eventText = (sse != null ? sse.format() :
|
CharSequence eventText = (sse != null ? sse.format() :
|
||||||
(fragment.viewName() != null ? "event:" + fragment.viewName() + "\n" : "") + "data:");
|
(fragment.viewName() != null ? "event:" + fragment.viewName() + "\n" : "") + "data: ");
|
||||||
|
|
||||||
DataBuffer prefix = encodeText(eventText.toString(), charset, bufferFactory);
|
DataBuffer prefix = encodeText(eventText.toString(), charset, bufferFactory);
|
||||||
DataBuffer suffix = encodeText("\n\n", charset, bufferFactory);
|
DataBuffer suffix = encodeText("\n\n", charset, bufferFactory);
|
||||||
|
|||||||
+16
-16
@@ -107,14 +107,14 @@ class FragmentViewResolutionResultHandlerTests {
|
|||||||
on(Handler.class).resolveReturnType(Flux.class, Fragment.class),
|
on(Handler.class).resolveReturnType(Flux.class, Fragment.class),
|
||||||
"""
|
"""
|
||||||
event:fragment1
|
event:fragment1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
event:fragment2
|
event:fragment2
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Bar
|
data: Hello Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@@ -126,10 +126,10 @@ class FragmentViewResolutionResultHandlerTests {
|
|||||||
on(Handler.class).resolveReturnType(Flux.class, Fragment.class),
|
on(Handler.class).resolveReturnType(Flux.class, Fragment.class),
|
||||||
"""
|
"""
|
||||||
event:fragment1
|
event:fragment1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data: and Bar
|
data: and Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@@ -147,15 +147,15 @@ class FragmentViewResolutionResultHandlerTests {
|
|||||||
"""
|
"""
|
||||||
id:id1
|
id:id1
|
||||||
event:event1
|
event:event1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
id:id2
|
id:id2
|
||||||
event:event2
|
event:event2
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Bar
|
data: Hello Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -177,6 +177,9 @@ final class SseServerResponse extends AbstractServerResponse {
|
|||||||
|
|
||||||
private SseBuilder field(String name, String value) {
|
private SseBuilder field(String name, String value) {
|
||||||
this.builder.append(name).append(':');
|
this.builder.append(name).append(':');
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
this.builder.append(' ');
|
||||||
|
}
|
||||||
SseUtils.appendFieldValue(name, value, this.builder);
|
SseUtils.appendFieldValue(name, value, this.builder);
|
||||||
this.builder.append('\n');
|
this.builder.append('\n');
|
||||||
return this;
|
return this;
|
||||||
@@ -200,7 +203,7 @@ final class SseServerResponse extends AbstractServerResponse {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void writeObject(Object data) throws IOException {
|
private void writeObject(Object data) throws IOException {
|
||||||
this.builder.append("data:");
|
this.builder.append("data: ");
|
||||||
try {
|
try {
|
||||||
this.outputMessage.getBody().write(builderBytes());
|
this.outputMessage.getBody().write(builderBytes());
|
||||||
Class<?> dataClass = data.getClass();
|
Class<?> dataClass = data.getClass();
|
||||||
|
|||||||
+1
-1
@@ -240,7 +240,7 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||||||
if (object instanceof ModelAndView mav && !this.hasName && mav.getViewName() != null) {
|
if (object instanceof ModelAndView mav && !this.hasName && mav.getViewName() != null) {
|
||||||
name(mav.getViewName());
|
name(mav.getViewName());
|
||||||
}
|
}
|
||||||
append("data:");
|
append("data: ");
|
||||||
saveAppendedText(TEXT_PLAIN);
|
saveAppendedText(TEXT_PLAIN);
|
||||||
if (object instanceof String text) {
|
if (object instanceof String text) {
|
||||||
writeStringData(text, mediaType);
|
writeStringData(text, mediaType);
|
||||||
|
|||||||
+8
-8
@@ -71,7 +71,7 @@ class SseServerResponseTests {
|
|||||||
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
||||||
assertThat(mav).isNull();
|
assertThat(mav).isNull();
|
||||||
|
|
||||||
String expected = "data:" + body + "\n\n";
|
String expected = "data: " + body + "\n\n";
|
||||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ class SseServerResponseTests {
|
|||||||
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
||||||
assertThat(mav).isNull();
|
assertThat(mav).isNull();
|
||||||
|
|
||||||
String expected = "data:{\"name\":\"John Doe\",\"age\":42}\n\n";
|
String expected = "data: {\"name\":\"John Doe\",\"age\":42}\n\n";
|
||||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,10 +151,10 @@ class SseServerResponseTests {
|
|||||||
assertThat(mav).isNull();
|
assertThat(mav).isNull();
|
||||||
|
|
||||||
String expected = """
|
String expected = """
|
||||||
data:{
|
data: {
|
||||||
data: "name" : "John Doe",
|
data: "name" : "John Doe",
|
||||||
data: "age" : 42
|
data: "age" : 42
|
||||||
data:}
|
data: }
|
||||||
|
|
||||||
""";
|
""";
|
||||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||||
@@ -186,7 +186,7 @@ class SseServerResponseTests {
|
|||||||
:comment line 1
|
:comment line 1
|
||||||
:comment line 2
|
:comment line 2
|
||||||
retry:1000
|
retry:1000
|
||||||
data:data
|
data: data
|
||||||
|
|
||||||
""";
|
""";
|
||||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||||
@@ -229,7 +229,7 @@ class SseServerResponseTests {
|
|||||||
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
||||||
assertThat(mav).isNull();
|
assertThat(mav).isNull();
|
||||||
|
|
||||||
String expected = "data:line1\ndata:line2\ndata:line3\n\n";
|
String expected = "data: line1\ndata: line2\ndata: line3\n\n";
|
||||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -104,14 +104,14 @@ class FragmentRenderingStreamTests {
|
|||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
||||||
event:fragment1
|
event:fragment1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
event:fragment2
|
event:fragment2
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Bar
|
data: Hello Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
"""));
|
"""));
|
||||||
}
|
}
|
||||||
@@ -134,14 +134,14 @@ class FragmentRenderingStreamTests {
|
|||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
||||||
event:fragment1
|
event:fragment1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
event:fragment2
|
event:fragment2
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Bar
|
data: Hello Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
"""));
|
"""));
|
||||||
}
|
}
|
||||||
@@ -163,10 +163,10 @@ class FragmentRenderingStreamTests {
|
|||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
assertThat(this.response.getContentAsString()).isEqualTo(("""
|
||||||
event:fragment1
|
event:fragment1
|
||||||
data:<p>
|
data: <p>
|
||||||
data: Hello Foo
|
data: Hello Foo
|
||||||
data: and Bar
|
data: and Bar
|
||||||
data:</p>
|
data: </p>
|
||||||
|
|
||||||
"""));
|
"""));
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -267,7 +267,7 @@ class ReactiveTypeHandlerTests {
|
|||||||
sink.tryEmitNext("baz");
|
sink.tryEmitNext("baz");
|
||||||
sink.tryEmitComplete();
|
sink.tryEmitComplete();
|
||||||
|
|
||||||
assertThat(emitterHandler.getValuesAsText()).isEqualTo("data:foo\n\ndata:bar\n\ndata:baz\n\n");
|
assertThat(emitterHandler.getValuesAsText()).isEqualTo("data: foo\n\ndata: bar\n\ndata: baz\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -286,7 +286,7 @@ class ReactiveTypeHandlerTests {
|
|||||||
sink.tryEmitNext(ServerSentEvent.builder("baz").id("3").build());
|
sink.tryEmitNext(ServerSentEvent.builder("baz").id("3").build());
|
||||||
sink.tryEmitComplete();
|
sink.tryEmitComplete();
|
||||||
|
|
||||||
assertThat(emitterHandler.getValuesAsText()).isEqualTo("id:1\ndata:foo\n\nid:2\ndata:bar\n\nid:3\ndata:baz\n\n");
|
assertThat(emitterHandler.getValuesAsText()).isEqualTo("id:1\ndata: foo\n\nid:2\ndata: bar\n\nid:3\ndata: baz\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@@ -453,7 +453,7 @@ class ReactiveTypeHandlerTests {
|
|||||||
|
|
||||||
sink.tryEmitComplete();
|
sink.tryEmitComplete();
|
||||||
|
|
||||||
assertThat(emitterHandler.getValuesAsText()).isEqualTo("data:emitted value\n\n");
|
assertThat(emitterHandler.getValuesAsText()).isEqualTo("data: emitted value\n\n");
|
||||||
assertThat(emitterHandler.getSavedRequest()).isSameAs(this.servletRequest);
|
assertThat(emitterHandler.getSavedRequest()).isSameAs(this.servletRequest);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
+1
-1
@@ -218,7 +218,7 @@ class RequestMappingHandlerAdapterTests {
|
|||||||
|
|
||||||
assertThat(this.response.getStatus()).isEqualTo(200);
|
assertThat(this.response.getStatus()).isEqualTo(200);
|
||||||
assertThat(this.response.getHeader("Content-Type")).isEqualTo("text/event-stream");
|
assertThat(this.response.getHeader("Content-Type")).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo("data:event 1\n\ndata:event 2\n\n");
|
assertThat(this.response.getContentAsString()).isEqualTo("data: event 1\n\ndata: event 2\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+5
-5
@@ -218,8 +218,8 @@ class ResponseBodyEmitterReturnValueHandlerTests {
|
|||||||
event:update
|
event:update
|
||||||
id:1
|
id:1
|
||||||
retry:5000
|
retry:5000
|
||||||
data:{"id":1,"name":"Joe"}
|
data: {"id":1,"name":"Joe"}
|
||||||
data:{"id":2,"name":"John"}
|
data: {"id":2,"name":"John"}
|
||||||
|
|
||||||
"""));
|
"""));
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ class ResponseBodyEmitterReturnValueHandlerTests {
|
|||||||
assertThat(this.response.getStatus()).isEqualTo(200);
|
assertThat(this.response.getStatus()).isEqualTo(200);
|
||||||
|
|
||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo("data:foo\n\ndata:bar\n\ndata:baz\n\n");
|
assertThat(this.response.getContentAsString()).isEqualTo("data: foo\n\ndata: bar\n\ndata: baz\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,7 +274,7 @@ class ResponseBodyEmitterReturnValueHandlerTests {
|
|||||||
assertThat(this.response.getStatus()).isEqualTo(200);
|
assertThat(this.response.getStatus()).isEqualTo(200);
|
||||||
|
|
||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo("data:foo123\n\ndata:bar123\n\ndata:baz123\n\n");
|
assertThat(this.response.getContentAsString()).isEqualTo("data: foo123\n\ndata: bar123\n\ndata: baz123\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-21972
|
@Test // gh-21972
|
||||||
@@ -357,7 +357,7 @@ class ResponseBodyEmitterReturnValueHandlerTests {
|
|||||||
assertThat(this.request.isAsyncStarted()).isTrue();
|
assertThat(this.request.isAsyncStarted()).isTrue();
|
||||||
assertThat(this.response.getStatus()).isEqualTo(200);
|
assertThat(this.response.getStatus()).isEqualTo(200);
|
||||||
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
assertThat(this.response.getContentType()).isEqualTo("text/event-stream");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo("data:foo\n\ndata:bar\n\n");
|
assertThat(this.response.getContentAsString()).isEqualTo("data: foo\n\ndata: bar\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // gh-36357
|
@Test // gh-36357
|
||||||
|
|||||||
+13
-13
@@ -65,7 +65,7 @@ class SseEmitterTests {
|
|||||||
void send() throws Exception {
|
void send() throws Exception {
|
||||||
this.emitter.send("foo");
|
this.emitter.send("foo");
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo");
|
this.handler.assertObject(1, "foo");
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -75,7 +75,7 @@ class SseEmitterTests {
|
|||||||
void sendWithMediaType() throws Exception {
|
void sendWithMediaType() throws Exception {
|
||||||
this.emitter.send("foo", MediaType.TEXT_PLAIN);
|
this.emitter.send("foo", MediaType.TEXT_PLAIN);
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo", MediaType.TEXT_PLAIN);
|
this.handler.assertObject(1, "foo", MediaType.TEXT_PLAIN);
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -92,7 +92,7 @@ class SseEmitterTests {
|
|||||||
void sendEventWithDataLine() throws Exception {
|
void sendEventWithDataLine() throws Exception {
|
||||||
this.emitter.send(event().data("foo"));
|
this.emitter.send(event().data("foo"));
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo");
|
this.handler.assertObject(1, "foo");
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -102,9 +102,9 @@ class SseEmitterTests {
|
|||||||
void sendEventWithTwoDataLines() throws Exception {
|
void sendEventWithTwoDataLines() throws Exception {
|
||||||
this.emitter.send(event().data("foo").data("bar"));
|
this.emitter.send(event().data("foo").data("bar"));
|
||||||
this.handler.assertSentObjectCount(5);
|
this.handler.assertSentObjectCount(5);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo");
|
this.handler.assertObject(1, "foo");
|
||||||
this.handler.assertObject(2, "\ndata:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\ndata: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(3, "bar");
|
this.handler.assertObject(3, "bar");
|
||||||
this.handler.assertObject(4, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(4, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -115,8 +115,8 @@ class SseEmitterTests {
|
|||||||
void sendEventWithMultiline(String newLineChars, String description) throws Exception {
|
void sendEventWithMultiline(String newLineChars, String description) throws Exception {
|
||||||
this.emitter.send(event().data("foo" + newLineChars + "bar" + newLineChars + "baz"));
|
this.emitter.send(event().data("foo" + newLineChars + "bar" + newLineChars + "baz"));
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo\ndata:bar\ndata:baz");
|
this.handler.assertObject(1, "foo\ndata: bar\ndata: baz");
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
}
|
}
|
||||||
@@ -126,8 +126,8 @@ class SseEmitterTests {
|
|||||||
void sendEventWithMultilineWithMediaType(String newLineChars, String description) throws Exception {
|
void sendEventWithMultilineWithMediaType(String newLineChars, String description) throws Exception {
|
||||||
this.emitter.send(event().data("foo" + newLineChars + "bar" + newLineChars + "baz", MediaType.TEXT_PLAIN));
|
this.emitter.send(event().data("foo" + newLineChars + "bar" + newLineChars + "baz", MediaType.TEXT_PLAIN));
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, "data:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, "data: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo\ndata:bar\ndata:baz", MediaType.TEXT_PLAIN);
|
this.handler.assertObject(1, "foo\ndata: bar\ndata: baz", MediaType.TEXT_PLAIN);
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ class SseEmitterTests {
|
|||||||
void sendEventFull() throws Exception {
|
void sendEventFull() throws Exception {
|
||||||
this.emitter.send(event().comment("blah").name("test").reconnectTime(5000L).id("1").data("foo"));
|
this.emitter.send(event().comment("blah").name("test").reconnectTime(5000L).id("1").data("foo"));
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, ":blah\nevent:test\nretry:5000\nid:1\ndata:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, ":blah\nevent:test\nretry:5000\nid:1\ndata: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo");
|
this.handler.assertObject(1, "foo");
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -146,9 +146,9 @@ class SseEmitterTests {
|
|||||||
void sendEventFullWithTwoDataLinesInTheMiddle() throws Exception {
|
void sendEventFullWithTwoDataLinesInTheMiddle() throws Exception {
|
||||||
this.emitter.send(event().comment("blah").data("foo").data("bar").name("test").reconnectTime(5000L).id("1"));
|
this.emitter.send(event().comment("blah").data("foo").data("bar").name("test").reconnectTime(5000L).id("1"));
|
||||||
this.handler.assertSentObjectCount(5);
|
this.handler.assertSentObjectCount(5);
|
||||||
this.handler.assertObject(0, ":blah\ndata:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, ":blah\ndata: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "foo");
|
this.handler.assertObject(1, "foo");
|
||||||
this.handler.assertObject(2, "\ndata:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\ndata: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(3, "bar");
|
this.handler.assertObject(3, "bar");
|
||||||
this.handler.assertObject(4, "\nevent:test\nretry:5000\nid:1\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(4, "\nevent:test\nretry:5000\nid:1\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
@@ -173,7 +173,7 @@ class SseEmitterTests {
|
|||||||
void supportMultiLineComments(String newLineChars, String description) throws Exception {
|
void supportMultiLineComments(String newLineChars, String description) throws Exception {
|
||||||
this.emitter.send(event().comment("foo" + newLineChars + "bar" + newLineChars + "baz").data("payload"));
|
this.emitter.send(event().comment("foo" + newLineChars + "bar" + newLineChars + "baz").data("payload"));
|
||||||
this.handler.assertSentObjectCount(3);
|
this.handler.assertSentObjectCount(3);
|
||||||
this.handler.assertObject(0, ":foo\n:bar\n:baz\ndata:", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(0, ":foo\n:bar\n:baz\ndata: ", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertObject(1, "payload");
|
this.handler.assertObject(1, "payload");
|
||||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||||
this.handler.assertWriteCount(1);
|
this.handler.assertWriteCount(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user