From 2735cba4b353027ac649b592bc9eb38c473b4fa1 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Wed, 30 Nov 2016 19:44:38 +0100 Subject: [PATCH] Append "data:" after line breaks for SSE JSON data fields Issue: SPR-14899 --- .../ServerSentEventHttpMessageWriter.java | 15 ++++++++++- .../http/codec/json/Jackson2JsonEncoder.java | 20 +++++++++++++- .../AbstractJackson2HttpMessageConverter.java | 26 ++++++++++++++---- ...ServerSentEventHttpMessageWriterTests.java | 27 ++++++++++++++++++- ...pingJackson2HttpMessageConverterTests.java | 14 ++++++++++ 5 files changed, 94 insertions(+), 8 deletions(-) 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 bfcbf0e66b4..3925702a4f2 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 @@ -19,6 +19,7 @@ package org.springframework.http.codec; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -48,6 +49,16 @@ import org.springframework.util.MimeTypeUtils; */ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter { + /** + * Server-Sent Events hint expecting a {@link Boolean} value which when set to true + * will adapt the content in order to comply with Server-Sent Events recommendation. + * For example, it will append "data:" after each line break with data encoders + * supporting it. + * @see Server-Sent Events W3C recommendation + */ + public static final String SSE_CONTENT_HINT = ServerSentEventHttpMessageWriter.class.getName() + ".sseContent"; + + private final List> dataEncoders; @@ -87,6 +98,8 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter> encode(Publisher inputStream, DataBufferFactory bufferFactory, ResolvableType type, Map hints) { + Map hintsWithSse = new HashMap<>(hints); + hintsWithSse.put(SSE_CONTENT_HINT, true); return Flux.from(inputStream) .map(o -> toSseEvent(o, type)) .map(sse -> { @@ -107,7 +120,7 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter> result = outputMessage.getBodyWithFlush(); StepVerifier.create(result) @@ -126,6 +128,29 @@ public class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAll .verify(); } + @Test // SPR-14899 + public void encodePojoWithPrettyPrint() { + ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().indentOutput(true).build(); + this.messageWriter = new ServerSentEventHttpMessageWriter(Collections.singletonList(new Jackson2JsonEncoder(mapper))); + + Flux source = Flux.just(new Pojo("foofoo", "barbar"), + new Pojo("foofoofoo", "barbarbar")); + MockServerHttpResponse outputMessage = new MockServerHttpResponse(); + messageWriter.write(source, ResolvableType.forClass(Pojo.class), + MediaType.TEXT_EVENT_STREAM, outputMessage, Collections.emptyMap()); + + Publisher> result = outputMessage.getBodyWithFlush(); + StepVerifier.create(result) + .consumeNextWith(sseConsumer("data:", "{\n" + + "data: \"foo\" : \"foofoo\",\n" + + "data: \"bar\" : \"barbar\"\n" + "data:}", "\n")) + .consumeNextWith(sseConsumer("data:", "{\n" + + "data: \"foo\" : \"foofoofoo\",\n" + + "data: \"bar\" : \"barbarbar\"\n" + "data:}", "\n")) + .expectComplete() + .verify(); + } + private Consumer> sseConsumer(String... expected) { return publisher -> { diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 8a2287ceaf5..a55bc4027b6 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -223,6 +223,20 @@ public class MappingJackson2HttpMessageConverterTests { assertEquals("{" + NEWLINE_SYSTEM_PROPERTY + " \"name\" : \"Jason\"" + NEWLINE_SYSTEM_PROPERTY + "}", result); } + @Test + public void prettyPrintWithSse() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM); + PrettyPrintBean bean = new PrettyPrintBean(); + bean.setName("Jason"); + + this.converter.setPrettyPrint(true); + this.converter.writeInternal(bean, null, outputMessage); + String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8); + + assertEquals("{\ndata: \"name\" : \"Jason\"\ndata:}", result); + } + @Test public void prefixJson() throws Exception { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();