String encoding for any MIME type

CharSequenceEncoder now supports all MIME types, however since encoding
Flux<String> can overlap with other encoders (e.g. SSE) there are now
two ways to create a CharSequenceEncoder -- with support for text/plain
only or with support for any MIME type.

In WebFlux configuration we insert one CharSequenceEncoder for
text/plain (as we have so far) and a second instance with support for
any MIME type at the very end.

Issue: SPR-15374
This commit is contained in:
Rossen Stoyanchev
2017-03-22 17:54:18 -04:00
parent 2896c5d2ab
commit 3d68c496f1
16 changed files with 106 additions and 39 deletions

View File

@@ -37,7 +37,6 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.util.Assert;
import static java.util.stream.Collectors.joining;
@@ -63,10 +62,18 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader<Objec
/**
* Constructor with JSON {@code Encoder} for encoding objects.
* Constructor without a {@code Decoder}. In this mode only {@code String}
* is supported as the data of an event.
*/
public ServerSentEventHttpMessageReader() {
this(null);
}
/**
* Constructor with JSON {@code Decoder} for decoding to Objects. Support
* for decoding to {@code String} event data is built-in.
*/
public ServerSentEventHttpMessageReader(Decoder<?> decoder) {
Assert.notNull(decoder, "Decoder must not be null");
this.decoder = decoder;
}
@@ -85,7 +92,7 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader<Objec
@Override
public boolean canRead(ResolvableType elementType, MediaType mediaType) {
return MediaType.TEXT_EVENT_STREAM.isCompatibleWith(mediaType) ||
return MediaType.TEXT_EVENT_STREAM.includes(mediaType) ||
ServerSentEvent.class.isAssignableFrom(elementType.getRawClass());
}
@@ -183,8 +190,6 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader<Objec
public Mono<Object> readMono(ResolvableType elementType, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
// For single String give StringDecoder a chance which comes after SSE in the order
if (String.class.equals(elementType.getRawClass())) {
Flux<DataBuffer> body = message.getBody();
return stringDecoder.decodeToMono(body, elementType, null, null).cast(Object.class);

View File

@@ -27,6 +27,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
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;
@@ -34,7 +35,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
/**
* {@code ServerHttpMessageWriter} for {@code "text/event-stream"} responses.
@@ -53,18 +53,25 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter
private final Encoder<?> encoder;
/**
* Constructor without an {@code Encoder}. In this mode only {@code String}
* is supported for event data to be encoded.
*/
public ServerSentEventHttpMessageWriter() {
this(null);
}
/**
* Constructor with JSON {@code Encoder} for encoding objects. Support for
* {@code String} event data is built-in.
*/
public ServerSentEventHttpMessageWriter(Encoder<?> encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
this.encoder = encoder;
}
/**
* Return the configured {@code Encoder}.
* Return the configured {@code Encoder}, possibly {@code null}.
*/
public Encoder<?> getEncoder() {
return this.encoder;
@@ -78,7 +85,7 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter
@Override
public boolean canWrite(ResolvableType elementType, MediaType mediaType) {
return mediaType == null || MediaType.TEXT_EVENT_STREAM.isCompatibleWith(mediaType) ||
return mediaType == null || MediaType.TEXT_EVENT_STREAM.includes(mediaType) ||
ServerSentEvent.class.isAssignableFrom(elementType.getRawClass());
}
@@ -135,6 +142,10 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter
return Flux.from(encodeText(text.replaceAll("\\n", "\ndata:") + "\n", factory));
}
if (this.encoder == null) {
return Flux.error(new CodecException("No SSE encoder configured and the data is not String."));
}
return ((Encoder<T>) this.encoder)
.encode(Mono.just((T) data), factory, valueType, MediaType.TEXT_EVENT_STREAM, hints)
.concatWith(encodeText("\n", factory));