Append "data:" after line breaks for SSE JSON data fields
Issue: SPR-14899
This commit is contained in:
@@ -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<Object> {
|
||||
|
||||
/**
|
||||
* 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 <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
|
||||
*/
|
||||
public static final String SSE_CONTENT_HINT = ServerSentEventHttpMessageWriter.class.getName() + ".sseContent";
|
||||
|
||||
|
||||
private final List<Encoder<?>> dataEncoders;
|
||||
|
||||
|
||||
@@ -87,6 +98,8 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
||||
private Flux<Publisher<DataBuffer>> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory,
|
||||
ResolvableType type, Map<String, Object> hints) {
|
||||
|
||||
Map<String, Object> 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<Objec
|
||||
return Flux.empty();
|
||||
}
|
||||
else {
|
||||
return applyEncoder(data, bufferFactory, hints);
|
||||
return applyEncoder(data, bufferFactory, hintsWithSse);
|
||||
}
|
||||
}).orElse(Flux.empty());
|
||||
|
||||
|
||||
@@ -22,9 +22,14 @@ import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.PrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -35,6 +40,7 @@ import org.springframework.core.codec.CodecException;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
@@ -57,12 +63,18 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Encode
|
||||
private static final ByteBuffer END_ARRAY_BUFFER = ByteBuffer.wrap(new byte[]{']'});
|
||||
|
||||
|
||||
private final PrettyPrinter ssePrettyPrinter;
|
||||
|
||||
|
||||
public Jackson2JsonEncoder() {
|
||||
super(Jackson2ObjectMapperBuilder.json().build());
|
||||
this(Jackson2ObjectMapperBuilder.json().build());
|
||||
}
|
||||
|
||||
public Jackson2JsonEncoder(ObjectMapper mapper) {
|
||||
super(mapper);
|
||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
||||
this.ssePrettyPrinter = prettyPrinter;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +135,12 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Encode
|
||||
writer = writer.forType(javaType);
|
||||
}
|
||||
|
||||
Boolean sse = (Boolean)hints.get(ServerSentEventHttpMessageWriter.SSE_CONTENT_HINT);
|
||||
SerializationConfig config = writer.getConfig();
|
||||
if (Boolean.TRUE.equals(sse) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
|
||||
writer = writer.with(this.ssePrettyPrinter);
|
||||
}
|
||||
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer();
|
||||
OutputStream outputStream = buffer.asOutputStream();
|
||||
try {
|
||||
|
||||
@@ -27,11 +27,14 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.PrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.ser.FilterProvider;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
@@ -69,22 +72,29 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
private PrettyPrinter ssePrettyPrinter;
|
||||
|
||||
|
||||
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
setDefaultCharset(DEFAULT_CHARSET);
|
||||
init(objectMapper);
|
||||
}
|
||||
|
||||
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {
|
||||
super(supportedMediaType);
|
||||
this.objectMapper = objectMapper;
|
||||
setDefaultCharset(DEFAULT_CHARSET);
|
||||
init(objectMapper);
|
||||
}
|
||||
|
||||
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
|
||||
super(supportedMediaTypes);
|
||||
init(objectMapper);
|
||||
}
|
||||
|
||||
protected void init(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
setDefaultCharset(DEFAULT_CHARSET);
|
||||
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
|
||||
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
|
||||
this.ssePrettyPrinter = prettyPrinter;
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +244,8 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
JsonEncoding encoding = getJsonEncoding(contentType);
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
|
||||
try {
|
||||
writePrefix(generator, object);
|
||||
@@ -265,6 +276,11 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
if (javaType != null && javaType.isContainerType()) {
|
||||
objectWriter = objectWriter.forType(javaType);
|
||||
}
|
||||
SerializationConfig config = objectWriter.getConfig();
|
||||
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
|
||||
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
|
||||
objectWriter = objectWriter.with(this.ssePrettyPrinter);
|
||||
}
|
||||
objectWriter.writeValue(generator, value);
|
||||
|
||||
writeSuffix(generator, object);
|
||||
|
||||
Reference in New Issue
Block a user