Append "data:" after line breaks for SSE JSON data fields

Issue: SPR-14899
This commit is contained in:
Sebastien Deleuze
2016-11-30 19:44:38 +01:00
parent 8315a4033f
commit 2735cba4b3
5 changed files with 94 additions and 8 deletions

View File

@@ -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());

View File

@@ -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 {

View File

@@ -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);