From 640e5705831beed32fd5c0490ef19c194ed62a95 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 10 Dec 2024 16:10:42 +0000 Subject: [PATCH] Minor refactoring in ServerSentEvent Extract re-usable method to serialize SSE fields. See gh-33975 --- .../http/codec/ServerSentEvent.java | 29 ++++++++++++++ .../ServerSentEventHttpMessageWriter.java | 38 ++++--------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEvent.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEvent.java index 8c988ee04f6b..397524427898 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEvent.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEvent.java @@ -20,6 +20,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Representation for a Server-Sent Event for use with Spring's reactive Web support. @@ -102,6 +103,34 @@ public T 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 public boolean equals(@Nullable Object other) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java index e23937943a94..28aac85286a9 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.http.codec; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.Map; @@ -124,38 +123,19 @@ private Flux> encode(Publisher input, ResolvableType el ServerSentEvent sse = (element instanceof ServerSentEvent serverSentEvent ? serverSentEvent : ServerSentEvent.builder().data(element).build()); - StringBuilder sb = new StringBuilder(); - String id = sse.id(); - String event = sse.event(); - Duration retry = sse.retry(); - String comment = sse.comment(); + String sseText = sse.format(); 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 result; 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) { 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 { - result = encodeEvent(sb, data, dataType, mediaType, factory, hints); + result = encodeEvent(sseText, data, dataType, mediaType, factory, hints); } return result.doOnDiscard(DataBuffer.class, DataBufferUtils::release); @@ -163,7 +143,7 @@ else if (data instanceof String text) { } @SuppressWarnings("unchecked") - private Flux encodeEvent(StringBuilder eventContent, T data, ResolvableType dataType, + private Flux encodeEvent(CharSequence sseText, T data, ResolvableType dataType, MediaType mediaType, DataBufferFactory factory, Map hints) { if (this.encoder == null) { @@ -171,7 +151,7 @@ private Flux encodeEvent(StringBuilder eventContent, T data, Res } return Flux.defer(() -> { - DataBuffer startBuffer = encodeText(eventContent, mediaType, factory); + DataBuffer startBuffer = encodeText(sseText, mediaType, factory); DataBuffer endBuffer = encodeText("\n\n", mediaType, factory); DataBuffer dataBuffer = ((Encoder) this.encoder).encodeValue(data, factory, dataType, mediaType, hints); Hints.touchDataBuffer(dataBuffer, hints, logger); @@ -179,10 +159,6 @@ private Flux encodeEvent(StringBuilder eventContent, T data, Res }); } - 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) { Assert.notNull(mediaType.getCharset(), "Expected MediaType with charset"); byte[] bytes = text.toString().getBytes(mediaType.getCharset());