mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Merge branch '7.0.x'
This commit is contained in:
@@ -22,7 +22,6 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Representation for a Server-Sent Event for use with Spring's reactive Web support.
|
||||
@@ -112,7 +111,9 @@ public final class ServerSentEvent<T> {
|
||||
appendAttribute("retry", this.retry.toMillis(), sb);
|
||||
}
|
||||
if (this.comment != null) {
|
||||
sb.append(':').append(StringUtils.replace(this.comment, "\n", "\n:")).append('\n');
|
||||
sb.append(':');
|
||||
appendEscaped(this.comment, "\n:", sb);
|
||||
sb.append('\n');
|
||||
}
|
||||
if (this.data != null) {
|
||||
sb.append("data:");
|
||||
@@ -124,6 +125,30 @@ public final class ServerSentEvent<T> {
|
||||
sb.append(fieldName).append(':').append(fieldValue).append('\n');
|
||||
}
|
||||
|
||||
private void appendEscaped(String input, String replacement, StringBuilder sb) {
|
||||
if (input.indexOf('\n') == -1 && input.indexOf('\r') == -1) {
|
||||
sb.append(input);
|
||||
}
|
||||
else {
|
||||
int length = input.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = input.charAt(i);
|
||||
if (c == '\r') {
|
||||
if (i + 1 < length && input.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
sb.append(replacement);
|
||||
}
|
||||
else if (c == '\n') {
|
||||
sb.append(replacement);
|
||||
}
|
||||
else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ServerSentEvent<?> that &&
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
@@ -44,6 +45,15 @@ class ServerSentEventTests {
|
||||
ServerSentEvent.<String>builder().event("first" + newLine + "second").build());
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest(name = "{1}")
|
||||
@MethodSource("newLineCharacters")
|
||||
void supportMultiLineComments(String newLine, String description) {
|
||||
ServerSentEvent<String> event = ServerSentEvent.<String>builder()
|
||||
.comment("foo" + newLine + "bar" + newLine + "baz").data("payload").build();
|
||||
assertThat(event.format()).isEqualTo(":foo\n:bar\n:baz\ndata:");
|
||||
}
|
||||
|
||||
private static Stream<Arguments> newLineCharacters() {
|
||||
return Stream.of(
|
||||
Arguments.of("\n", "LF"),
|
||||
|
||||
+16
-5
@@ -224,7 +224,9 @@ public class SseEmitter extends ResponseBodyEmitter {
|
||||
|
||||
@Override
|
||||
public SseEventBuilder comment(String comment) {
|
||||
append(':').append(StringUtils.replace(comment, "\n", "\n:")).append('\n');
|
||||
append(':');
|
||||
appendEscaped(comment, "\n:");
|
||||
append('\n');
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -259,6 +261,16 @@ public class SseEmitter extends ResponseBodyEmitter {
|
||||
if (input.indexOf('\n') == -1 && input.indexOf('\r') == -1) {
|
||||
this.dataToSend.add(new DataWithMediaType(input, mediaType));
|
||||
}
|
||||
else {
|
||||
appendEscaped(input, "\ndata:");
|
||||
saveAppendedText(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendEscaped(String input, String replacement) {
|
||||
if (input.indexOf('\n') == -1 && input.indexOf('\r') == -1) {
|
||||
append(input);
|
||||
}
|
||||
else {
|
||||
int length = input.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
@@ -267,16 +279,15 @@ public class SseEmitter extends ResponseBodyEmitter {
|
||||
if (i + 1 < length && input.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
this.sb.append("\ndata:");
|
||||
append(replacement);
|
||||
}
|
||||
else if (c == '\n') {
|
||||
this.sb.append("\ndata:");
|
||||
append(replacement);
|
||||
}
|
||||
else {
|
||||
this.sb.append(c);
|
||||
append(c);
|
||||
}
|
||||
}
|
||||
saveAppendedText(mediaType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -168,6 +168,17 @@ class SseEmitterTests {
|
||||
.send(event().name("first" + newLineChars + "second")));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{1}")
|
||||
@MethodSource("newLineCharacters")
|
||||
void supportMultiLineComments(String newLineChars, String description) throws Exception {
|
||||
this.emitter.send(event().comment("foo" + newLineChars + "bar" + newLineChars + "baz").data("payload"));
|
||||
this.handler.assertSentObjectCount(3);
|
||||
this.handler.assertObject(0, ":foo\n:bar\n:baz\ndata:", TEXT_PLAIN_UTF8);
|
||||
this.handler.assertObject(1, "payload");
|
||||
this.handler.assertObject(2, "\n\n", TEXT_PLAIN_UTF8);
|
||||
this.handler.assertWriteCount(1);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> newLineCharacters() {
|
||||
return Stream.of(
|
||||
Arguments.of("\n", "LF"),
|
||||
|
||||
Reference in New Issue
Block a user