From 20dec61d0425d034baea738fb044fc8b5e28c4b3 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 21 Oct 2016 16:55:38 +0200 Subject: [PATCH] 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 --- .../DefaultHandlerStrategiesBuilder.java | 13 ++- .../http/codec/BodyInserters.java | 101 +++++++++--------- .../DefaultWebClientStrategiesBuilder.java | 4 + .../http/codec/BodyInsertersTests.java | 8 +- 4 files changed, 76 insertions(+), 50 deletions(-) diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultHandlerStrategiesBuilder.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultHandlerStrategiesBuilder.java index 699d6e7198..4c09180dc3 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultHandlerStrategiesBuilder.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/function/DefaultHandlerStrategiesBuilder.java @@ -33,6 +33,8 @@ import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; 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.Jackson2JsonEncoder; 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 ByteBufferDecoder())); messageReader(new DecoderHttpMessageReader<>(new StringDecoder())); + messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); + messageWriter(new ResourceHttpMessageWriter()); + if (jaxb2Present) { messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); } if (jackson2Present) { 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()); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java index df2d654c33..d099e2be79 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java +++ b/spring-web/src/main/java/org/springframework/http/codec/BodyInserters.java @@ -30,10 +30,8 @@ import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * 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 = 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. * @param body the body of the response @@ -63,8 +55,7 @@ public abstract class BodyInserters { public static BodyInserter fromObject(T body) { Assert.notNull(body, "'body' must not be null"); return BodyInserter.of( - (response, context) -> writeWithMessageWriters(response, context, - Mono.just(body), ResolvableType.forInstance(body)), + writeFunctionFor(Mono.just(body), ResolvableType.forInstance(body)), () -> body); } @@ -81,7 +72,10 @@ public abstract class BodyInserters { Assert.notNull(publisher, "'publisher' 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(elementType, "'elementType' must not be null"); return BodyInserter.of( - (response, context) -> writeWithMessageWriters(response, context, - publisher, elementType), + writeFunctionFor(publisher, elementType), () -> publisher ); } @@ -117,15 +110,23 @@ public abstract class BodyInserters { Assert.notNull(resource, "'resource' must not be null"); return BodyInserter.of( (response, context) -> { - ResourceHttpMessageWriter messageWriter = new ResourceHttpMessageWriter(); - MediaType contentType = response.getHeaders().getContentType(); - return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, contentType, + HttpMessageWriter messageWriter = resourceHttpMessageWriter(context); + return messageWriter.write(Mono.just(resource), RESOURCE_TYPE, null, response, Collections.emptyMap()); }, () -> resource ); } + private static HttpMessageWriter resourceHttpMessageWriter(BodyInserter.Context context) { + return context.messageWriters().get() + .filter(messageWriter -> messageWriter.canWrite(RESOURCE_TYPE, null)) + .findFirst() + .map(BodyInserters::cast) + .orElseThrow(() -> new IllegalStateException( + "Could not find HttpMessageWriter that supports Resources.")); + } + /** * Return a {@code BodyInserter} that writes the given {@code ServerSentEvent} publisher. * @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"); return BodyInserter.of( (response, context) -> { - ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); - MediaType contentType = response.getHeaders().getContentType(); + HttpMessageWriter> messageWriter = sseMessageWriter(context); return messageWriter.write(eventsPublisher, SERVER_SIDE_EVENT_TYPE, - contentType, response, Collections.emptyMap()); + MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap()); }, () -> eventsPublisher ); @@ -183,44 +183,49 @@ public abstract class BodyInserters { Assert.notNull(eventType, "'eventType' must not be null"); return BodyInserter.of( (response, context) -> { - ServerSentEventHttpMessageWriter messageWriter = sseMessageWriter(); - MediaType contentType = response.getHeaders().getContentType(); - return messageWriter.write(eventsPublisher, eventType, contentType, response, - Collections.emptyMap()); + HttpMessageWriter messageWriter = sseMessageWriter(context); + return messageWriter.write(eventsPublisher, eventType, + MediaType.TEXT_EVENT_STREAM, response, Collections.emptyMap()); }, () -> eventsPublisher ); } - private static ServerSentEventHttpMessageWriter sseMessageWriter() { - return jackson2Present ? new ServerSentEventHttpMessageWriter( - Collections.singletonList(new Jackson2JsonEncoder())) : - new ServerSentEventHttpMessageWriter(); + private static HttpMessageWriter sseMessageWriter(BodyInserter.Context context) { + return context.messageWriters().get() + .filter(messageWriter -> messageWriter + .canWrite(SERVER_SIDE_EVENT_TYPE, MediaType.TEXT_EVENT_STREAM)) + .findFirst() + .map(BodyInserters::cast) + .orElseThrow(() -> new IllegalStateException( + "Could not find HttpMessageWriter that supports " + + MediaType.TEXT_EVENT_STREAM_VALUE)); } - private static Mono writeWithMessageWriters(ReactiveHttpOutputMessage outputMessage, - BodyInserter.Context context, - Publisher body, - ResolvableType bodyType) { + private static BiFunction> + writeFunctionFor(Publisher body, ResolvableType bodyType) { - MediaType contentType = outputMessage.getHeaders().getContentType(); - Supplier>> messageWriters = context.messageWriters(); - return messageWriters.get() - .filter(messageWriter -> messageWriter.canWrite(bodyType, contentType)) - .findFirst() - .map(BodyInserters::cast) - .map(messageWriter -> messageWriter - .write(body, bodyType, contentType, outputMessage, Collections - .emptyMap())) - .orElseGet(() -> { - List supportedMediaTypes = messageWriters.get() - .flatMap(reader -> reader.getWritableMediaTypes().stream()) - .collect(Collectors.toList()); - UnsupportedMediaTypeException error = - new UnsupportedMediaTypeException(contentType, supportedMediaTypes); - return Mono.error(error); - }); + return (m, context) -> { + + MediaType contentType = m.getHeaders().getContentType(); + Supplier>> messageWriters = context.messageWriters(); + return messageWriters.get() + .filter(messageWriter -> messageWriter.canWrite(bodyType, contentType)) + .findFirst() + .map(BodyInserters::cast) + .map(messageWriter -> messageWriter + .write(body, bodyType, contentType, m, Collections + .emptyMap())) + .orElseGet(() -> { + List supportedMediaTypes = messageWriters.get() + .flatMap(reader -> reader.getWritableMediaTypes().stream()) + .collect(Collectors.toList()); + UnsupportedMediaTypeException error = + new UnsupportedMediaTypeException(contentType, supportedMediaTypes); + return Mono.error(error); + }); + }; } @SuppressWarnings("unchecked") diff --git a/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultWebClientStrategiesBuilder.java b/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultWebClientStrategiesBuilder.java index b81f361a9a..bdee2cd1c3 100644 --- a/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultWebClientStrategiesBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/client/reactive/DefaultWebClientStrategiesBuilder.java @@ -35,6 +35,7 @@ import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageReader; 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.Jackson2JsonEncoder; 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 ByteBufferDecoder())); messageReader(new DecoderHttpMessageReader<>(new StringDecoder(false))); + messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); + messageWriter(new ResourceHttpMessageWriter()); + if (jaxb2Present) { messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); diff --git a/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java index c6afe90dcc..e0ebf40300 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/BodyInsertersTests.java @@ -19,6 +19,7 @@ package org.springframework.http.codec; import java.nio.ByteBuffer; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; @@ -57,8 +58,13 @@ public class BodyInsertersTests { final List> messageWriters = new ArrayList<>(); messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); + messageWriters.add(new ResourceHttpMessageWriter()); 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() { @Override