Restore use of Flux-based encode method in HTTP
Closes gh-24441
This commit is contained in:
parent
a03a116f6b
commit
df706f4c7c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -78,9 +78,9 @@ public interface Decoder<T> {
|
|||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints);
|
||||
|
||||
/**
|
||||
* Decode a data buffer to an Object of type T. This is useful when the input
|
||||
* stream consists of discrete messages (or events) and the content for each
|
||||
* can be decoded on its own.
|
||||
* Decode a data buffer to an Object of type T. This is useful for scenarios,
|
||||
* that distinct messages (or events) are decoded and handled individually,
|
||||
* in fully aggregated form.
|
||||
* @param buffer the {@code DataBuffer} to decode
|
||||
* @param targetType the expected output type
|
||||
* @param mimeType the MIME type associated with the data
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -68,9 +68,9 @@ public interface Encoder<T> {
|
|||
ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints);
|
||||
|
||||
/**
|
||||
* Encode an Object of type T to a data buffer. This is useful for scenarios
|
||||
* that produce a stream of discrete messages (or events) and the
|
||||
* content for each is encoded individually.
|
||||
* Encode an Object of type T to a data buffer. This is useful for scenarios,
|
||||
* that distinct messages (or events) are encoded and handled individually,
|
||||
* in fully aggregated form.
|
||||
* <p>By default this method raises {@link UnsupportedOperationException}
|
||||
* and it is expected that some encoders cannot produce a single buffer or
|
||||
* cannot do so synchronously (e.g. encoding a {@code Resource}).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -29,7 +29,6 @@ import org.springframework.core.codec.AbstractEncoder;
|
|||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.Hints;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.http.HttpLogging;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -114,24 +113,23 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
|||
|
||||
MediaType contentType = updateContentType(message, mediaType);
|
||||
|
||||
Flux<DataBuffer> body = this.encoder.encode(
|
||||
inputStream, message.bufferFactory(), elementType, contentType, hints);
|
||||
|
||||
if (inputStream instanceof Mono) {
|
||||
return Mono.from(inputStream)
|
||||
return body
|
||||
.singleOrEmpty()
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
message.getHeaders().setContentLength(0);
|
||||
return message.setComplete().then(Mono.empty());
|
||||
}))
|
||||
.flatMap(value -> {
|
||||
DataBufferFactory factory = message.bufferFactory();
|
||||
DataBuffer buffer = this.encoder.encodeValue(value, factory, elementType, contentType, hints);
|
||||
.flatMap(buffer -> {
|
||||
message.getHeaders().setContentLength(buffer.readableByteCount());
|
||||
return message.writeWith(Mono.just(buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release));
|
||||
});
|
||||
}
|
||||
|
||||
Flux<DataBuffer> body = this.encoder.encode(
|
||||
inputStream, message.bufferFactory(), elementType, contentType, hints);
|
||||
|
||||
if (isStreamingMediaType(contentType)) {
|
||||
return message.writeAndFlushWith(body.map(buffer ->
|
||||
Mono.just(buffer).doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release)));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -18,7 +18,7 @@ package org.springframework.http.codec;
|
|||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -112,7 +112,7 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
|||
}
|
||||
|
||||
private Flux<Publisher<DataBuffer>> encode(Publisher<?> input, ResolvableType elementType,
|
||||
MediaType mediaType, DataBufferFactory bufferFactory, Map<String, Object> hints) {
|
||||
MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) {
|
||||
|
||||
ResolvableType dataType = (ServerSentEvent.class.isAssignableFrom(elementType.toClass()) ?
|
||||
elementType.getGeneric() : elementType);
|
||||
|
@ -144,13 +144,35 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
|||
sb.append("data:");
|
||||
}
|
||||
|
||||
Mono<DataBuffer> bufferMono = Mono.fromCallable(() ->
|
||||
bufferFactory.join(encodeEvent(sb, data, dataType, mediaType, bufferFactory, hints)));
|
||||
Flux<DataBuffer> result;
|
||||
if (data == null) {
|
||||
result = Flux.just(encodeText(sb + "\n", mediaType, factory));
|
||||
}
|
||||
else if (data instanceof String) {
|
||||
data = StringUtils.replace((String) data, "\n", "\ndata:");
|
||||
result = Flux.just(encodeText(sb + (String) data + "\n\n", mediaType, factory));
|
||||
}
|
||||
else {
|
||||
result = encodeEvent(sb.toString(), data, dataType, mediaType, factory, hints);
|
||||
}
|
||||
|
||||
return bufferMono.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
return result.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Flux<DataBuffer> encodeEvent(String eventContent, T data, ResolvableType dataType,
|
||||
MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) {
|
||||
|
||||
if (this.encoder == null) {
|
||||
throw new CodecException("No SSE encoder configured and the data is not String.");
|
||||
}
|
||||
return Flux.just(factory.join(Arrays.asList(
|
||||
encodeText(eventContent, mediaType, factory),
|
||||
((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints),
|
||||
encodeText("\n\n", mediaType, factory))));
|
||||
}
|
||||
|
||||
private void writeField(String fieldName, Object fieldValue, StringBuilder sb) {
|
||||
sb.append(fieldName);
|
||||
sb.append(':');
|
||||
|
@ -158,29 +180,6 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
|||
sb.append("\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<DataBuffer> encodeEvent(CharSequence markup, @Nullable T data, ResolvableType dataType,
|
||||
MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) {
|
||||
|
||||
List<DataBuffer> result = new ArrayList<>(4);
|
||||
result.add(encodeText(markup, mediaType, factory));
|
||||
if (data != null) {
|
||||
if (data instanceof String) {
|
||||
String dataLine = StringUtils.replace((String) data, "\n", "\ndata:") + "\n";
|
||||
result.add(encodeText(dataLine, mediaType, factory));
|
||||
}
|
||||
else if (this.encoder == null) {
|
||||
throw new CodecException("No SSE encoder configured and the data is not String.");
|
||||
}
|
||||
else {
|
||||
result.add(((Encoder<T>) this.encoder).encodeValue(data, factory, dataType, mediaType, hints));
|
||||
result.add(encodeText("\n", mediaType, factory));
|
||||
}
|
||||
}
|
||||
result.add(encodeText("\n", mediaType, factory));
|
||||
return result;
|
||||
}
|
||||
|
||||
private DataBuffer encodeText(CharSequence text, MediaType mediaType, DataBufferFactory bufferFactory) {
|
||||
Assert.notNull(mediaType.getCharset(), "Expected MediaType with charset");
|
||||
byte[] bytes = text.toString().getBytes(mediaType.getCharset());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
|
@ -157,7 +157,7 @@ class EncoderHttpMessageWriterTests {
|
|||
void setContentLengthForMonoBody() {
|
||||
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
|
||||
DataBuffer buffer = factory.wrap("body".getBytes(StandardCharsets.UTF_8));
|
||||
configureEncoder(buffer, MimeTypeUtils.TEXT_PLAIN);
|
||||
configureEncoder(Flux.just(buffer), MimeTypeUtils.TEXT_PLAIN);
|
||||
HttpMessageWriter<String> writer = new EncoderHttpMessageWriter<>(this.encoder);
|
||||
writer.write(Mono.just("body"), forClass(String.class), TEXT_PLAIN, this.response, NO_HINTS).block();
|
||||
|
||||
|
|
Loading…
Reference in New Issue