Minor refactoring in ServerSentEvent

Extract re-usable method to serialize SSE fields.

See gh-33975
This commit is contained in:
rstoyanchev 2024-12-10 16:10:42 +00:00
parent fd8823819f
commit c4b100ac0c
2 changed files with 36 additions and 31 deletions

View File

@ -20,6 +20,7 @@ import java.time.Duration;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
* Representation for a Server-Sent Event for use with Spring's reactive Web support. * Representation for a Server-Sent Event for use with Spring's reactive Web support.
@ -102,6 +103,34 @@ public final class ServerSentEvent<T> {
return this.data; return this.data;
} }
/**
* Return a StringBuilder with the id, event, retry, and comment fields fully
* serialized, and also appending "data:" if there is data.
* @since 6.2.1
*/
public String format() {
StringBuilder sb = new StringBuilder();
if (this.id != null) {
appendAttribute("id", this.id, sb);
}
if (this.event != null) {
appendAttribute("event", this.event, sb);
}
if (this.retry != null) {
appendAttribute("retry", this.retry.toMillis(), sb);
}
if (this.comment != null) {
sb.append(':').append(StringUtils.replace(this.comment, "\n", "\n:")).append('\n');
}
if (this.data != null) {
sb.append("data:");
}
return sb.toString();
}
private void appendAttribute(String fieldName, Object fieldValue, StringBuilder sb) {
sb.append(fieldName).append(':').append(fieldValue).append('\n');
}
@Override @Override
public boolean equals(@Nullable Object other) { public boolean equals(@Nullable Object other) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,7 +17,6 @@
package org.springframework.http.codec; package org.springframework.http.codec;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -124,38 +123,19 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
ServerSentEvent<?> sse = (element instanceof ServerSentEvent<?> serverSentEvent ? ServerSentEvent<?> sse = (element instanceof ServerSentEvent<?> serverSentEvent ?
serverSentEvent : ServerSentEvent.builder().data(element).build()); serverSentEvent : ServerSentEvent.builder().data(element).build());
StringBuilder sb = new StringBuilder(); String sseText = sse.format();
String id = sse.id();
String event = sse.event();
Duration retry = sse.retry();
String comment = sse.comment();
Object data = sse.data(); Object data = sse.data();
if (id != null) {
writeField("id", id, sb);
}
if (event != null) {
writeField("event", event, sb);
}
if (retry != null) {
writeField("retry", retry.toMillis(), sb);
}
if (comment != null) {
sb.append(':').append(StringUtils.replace(comment, "\n", "\n:")).append('\n');
}
if (data != null) {
sb.append("data:");
}
Flux<DataBuffer> result; Flux<DataBuffer> result;
if (data == null) { if (data == null) {
result = Flux.just(encodeText(sb + "\n", mediaType, factory)); result = Flux.just(encodeText(sseText + "\n", mediaType, factory));
} }
else if (data instanceof String text) { else if (data instanceof String text) {
text = StringUtils.replace(text, "\n", "\ndata:"); text = StringUtils.replace(text, "\n", "\ndata:");
result = Flux.just(encodeText(sb + text + "\n\n", mediaType, factory)); result = Flux.just(encodeText(sseText + text + "\n\n", mediaType, factory));
} }
else { else {
result = encodeEvent(sb, data, dataType, mediaType, factory, hints); result = encodeEvent(sseText, data, dataType, mediaType, factory, hints);
} }
return result.doOnDiscard(DataBuffer.class, DataBufferUtils::release); return result.doOnDiscard(DataBuffer.class, DataBufferUtils::release);
@ -163,7 +143,7 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> Flux<DataBuffer> encodeEvent(StringBuilder eventContent, T data, ResolvableType dataType, private <T> Flux<DataBuffer> encodeEvent(CharSequence sseText, T data, ResolvableType dataType,
MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) {
if (this.encoder == null) { if (this.encoder == null) {
@ -171,7 +151,7 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
} }
return Flux.defer(() -> { return Flux.defer(() -> {
DataBuffer startBuffer = encodeText(eventContent, mediaType, factory); DataBuffer startBuffer = encodeText(sseText, mediaType, factory);
DataBuffer endBuffer = encodeText("\n\n", mediaType, factory); DataBuffer endBuffer = encodeText("\n\n", mediaType, factory);
DataBuffer dataBuffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints); DataBuffer dataBuffer = ((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints);
Hints.touchDataBuffer(dataBuffer, hints, logger); Hints.touchDataBuffer(dataBuffer, hints, logger);
@ -179,10 +159,6 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
}); });
} }
private void writeField(String fieldName, Object fieldValue, StringBuilder sb) {
sb.append(fieldName).append(':').append(fieldValue).append('\n');
}
private DataBuffer encodeText(CharSequence text, MediaType mediaType, DataBufferFactory bufferFactory) { private DataBuffer encodeText(CharSequence text, MediaType mediaType, DataBufferFactory bufferFactory) {
Assert.notNull(mediaType.getCharset(), "Expected MediaType with charset"); Assert.notNull(mediaType.getCharset(), "Expected MediaType with charset");
byte[] bytes = text.toString().getBytes(mediaType.getCharset()); byte[] bytes = text.toString().getBytes(mediaType.getCharset());