Refactored BodyInserters

This commit introduces a couple of changes to BodyInserters:

- Refactored writeWithMessageWriters into BiFunction
- BodyInserters.fromResource now uses ResourceMessagewriter from context
- BodyInserters.fromServerSentEvents now uses SseHttpMessageWriter from context
This commit is contained in:
Arjen Poutsma 2016-10-21 16:55:38 +02:00
parent 94930043fd
commit 20dec61d04
4 changed files with 76 additions and 50 deletions

View File

@ -33,6 +33,8 @@ import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
@ -70,16 +72,25 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReader(new DecoderHttpMessageReader<>(new StringDecoder())); messageReader(new DecoderHttpMessageReader<>(new StringDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriter(new ResourceHttpMessageWriter());
if (jaxb2Present) { if (jaxb2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
} }
if (jackson2Present) { if (jackson2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
messageWriter(new EncoderHttpMessageWriter<>(jsonEncoder));
messageWriter(
new ServerSentEventHttpMessageWriter(Collections.singletonList(jsonEncoder)));
}
else {
messageWriter(new ServerSentEventHttpMessageWriter());
} }
} }

View File

@ -30,10 +30,8 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/** /**
* Implementations of {@link BodyInserter} that write various bodies, such a reactive streams, * Implementations of {@link BodyInserter} that write various bodies, such a reactive streams,
@ -49,12 +47,6 @@ public abstract class BodyInserters {
private static final ResolvableType SERVER_SIDE_EVENT_TYPE = private static final ResolvableType SERVER_SIDE_EVENT_TYPE =
ResolvableType.forClass(ServerSentEvent.class); ResolvableType.forClass(ServerSentEvent.class);
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
BodyInserters.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
BodyInserters.class.getClassLoader());
/** /**
* Return a {@code BodyInserter} that writes the given single object. * Return a {@code BodyInserter} that writes the given single object.
* @param body the body of the response * @param body the body of the response
@ -63,8 +55,7 @@ public abstract class BodyInserters {
public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) { public static <T> BodyInserter<T, ReactiveHttpOutputMessage> fromObject(T body) {
Assert.notNull(body, "'body' must not be null"); Assert.notNull(body, "'body' must not be null");
return BodyInserter.of( return BodyInserter.of(
(response, context) -> writeWithMessageWriters(response, context, writeFunctionFor(Mono.just(body), ResolvableType.forInstance(body)),
Mono.just(body), ResolvableType.forInstance(body)),
() -> body); () -> body);
} }
@ -81,7 +72,10 @@ public abstract class BodyInserters {
Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(publisher, "'publisher' must not be null");
Assert.notNull(elementClass, "'elementClass' must not be null"); Assert.notNull(elementClass, "'elementClass' must not be null");
return fromPublisher(publisher, ResolvableType.forClass(elementClass)); return BodyInserter.of(
writeFunctionFor(publisher, ResolvableType.forClass(elementClass)),
() -> publisher
);
} }
/** /**
@ -98,8 +92,7 @@ public abstract class BodyInserters {
Assert.notNull(publisher, "'publisher' must not be null"); Assert.notNull(publisher, "'publisher' must not be null");
Assert.notNull(elementType, "'elementType' must not be null"); Assert.notNull(elementType, "'elementType' must not be null");
return BodyInserter.of( return BodyInserter.of(
(response, context) -> writeWithMessageWriters(response, context, writeFunctionFor(publisher, elementType),
publisher, elementType),
() -> publisher () -> publisher
); );
} }
@ -117,15 +110,23 @@ public abstract class BodyInserters {
Assert.notNull(resource, "'resource' must not be null"); Assert.notNull(resource, "'resource' must not be null");
return BodyInserter.of( return BodyInserter.of(
(response, context) -> { (response, context) -> {
ResourceHttpMessageWriter messageWriter = new ResourceHttpMessageWriter(); HttpMessageWriter<Resource> messageWriter = resourceHttpMessageWriter(context);
MediaType contentType = response.getHeaders().getContentType(); return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null,
return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, contentType,
response, Collections.emptyMap()); response, Collections.emptyMap());
}, },
() -> resource () -> resource
); );
} }
private static HttpMessageWriter<Resource> resourceHttpMessageWriter(BodyInserter.Context context) {
return context.messageWriters().get()
.filter(messageWriter -> messageWriter.canWrite(RESOURCE_TYPE, null))
.findFirst()
.map(BodyInserters::<Resource>cast)
.orElseThrow(() -> new IllegalStateException(
"Could not find HttpMessageWriter that supports Resources."));
}
/** /**
* Return a {@code BodyInserter} that writes the given {@code ServerSentEvent} publisher. * Return a {@code BodyInserter} that writes the given {@code ServerSentEvent} publisher.
* @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body * @param eventsPublisher the {@code ServerSentEvent} publisher to write to the response body
@ -139,10 +140,9 @@ public abstract class BodyInserters {
Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null"); Assert.notNull(eventsPublisher, "'eventsPublisher' must not be null");
return BodyInserter.of( return BodyInserter.of(
(response, context) -> { (response, context) -> {
ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); HttpMessageWriter<ServerSentEvent<T>> messageWriter = sseMessageWriter(context);
MediaType contentType = response.getHeaders().getContentType();
return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE, return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE,
contentType, response, Collections.emptyMap()); MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap());
}, },
() -> eventsPublisher () -> eventsPublisher
); );
@ -183,35 +183,39 @@ public abstract class BodyInserters {
Assert.notNull(eventType, "'eventType' must not be null"); Assert.notNull(eventType, "'eventType' must not be null");
return BodyInserter.of( return BodyInserter.of(
(response, context) -> { (response, context) -> {
ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); HttpMessageWriter<T> messageWriter = sseMessageWriter(context);
MediaType contentType = response.getHeaders().getContentType(); return messageWriter.write(eventsPublisher, eventType,
return messageWriter.write(eventsPublisher, eventType, contentType, response, MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap());
Collections.emptyMap());
}, },
() -> eventsPublisher () -> eventsPublisher
); );
} }
private static ServerSentEventHttpMessageWriter sseMessageWriter() { private static <T> HttpMessageWriter<T> sseMessageWriter(BodyInserter.Context context) {
return jackson2Present ? new ServerSentEventHttpMessageWriter( return context.messageWriters().get()
Collections.singletonList(new Jackson2JsonEncoder())) : .filter(messageWriter -> messageWriter
new ServerSentEventHttpMessageWriter(); .canWrite(SERVER_SIDE_EVENT_TYPE, MediaType.TEXT_EVENT_STREAM))
.findFirst()
.map(BodyInserters::<T>cast)
.orElseThrow(() -> new IllegalStateException(
"Could not find HttpMessageWriter that supports " +
MediaType.TEXT_EVENT_STREAM_VALUE));
} }
private static <T> Mono<Void> writeWithMessageWriters(ReactiveHttpOutputMessage outputMessage, private static <T, M extends ReactiveHttpOutputMessage> BiFunction<M, BodyInserter.Context, Mono<Void>>
BodyInserter.Context context, writeFunctionFor(Publisher<T> body, ResolvableType bodyType) {
Publisher<T> body,
ResolvableType bodyType) {
MediaType contentType = outputMessage.getHeaders().getContentType(); return (m, context) -> {
MediaType contentType = m.getHeaders().getContentType();
Supplier<Stream<HttpMessageWriter<?>>> messageWriters = context.messageWriters(); Supplier<Stream<HttpMessageWriter<?>>> messageWriters = context.messageWriters();
return messageWriters.get() return messageWriters.get()
.filter(messageWriter -> messageWriter.canWrite(bodyType, contentType)) .filter(messageWriter -> messageWriter.canWrite(bodyType, contentType))
.findFirst() .findFirst()
.map(BodyInserters::cast) .map(BodyInserters::cast)
.map(messageWriter -> messageWriter .map(messageWriter -> messageWriter
.write(body, bodyType, contentType, outputMessage, Collections .write(body, bodyType, contentType, m, Collections
.emptyMap())) .emptyMap()))
.orElseGet(() -> { .orElseGet(() -> {
List<MediaType> supportedMediaTypes = messageWriters.get() List<MediaType> supportedMediaTypes = messageWriters.get()
@ -221,6 +225,7 @@ public abstract class BodyInserters {
new UnsupportedMediaTypeException(contentType, supportedMediaTypes); new UnsupportedMediaTypeException(contentType, supportedMediaTypes);
return Mono.error(error); return Mono.error(error);
}); });
};
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -35,6 +35,7 @@ import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
@ -70,9 +71,12 @@ class DefaultWebClientStrategiesBuilder implements WebClientStrategies.Builder {
messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReader(new DecoderHttpMessageReader<>(new StringDecoder(false))); messageReader(new DecoderHttpMessageReader<>(new StringDecoder(false)));
messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriter(new ResourceHttpMessageWriter());
if (jaxb2Present) { if (jaxb2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));

View File

@ -19,6 +19,7 @@ package org.springframework.http.codec;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -57,8 +58,13 @@ public class BodyInsertersTests {
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>(); final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriters.add(new ResourceHttpMessageWriter());
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
messageWriters.add(new EncoderHttpMessageWriter<>(jsonEncoder));
messageWriters
.add(new ServerSentEventHttpMessageWriter(Collections.singletonList(jsonEncoder)));
this.context = new BodyInserter.Context() { this.context = new BodyInserter.Context() {
@Override @Override