From 5f8bc4552f487d5a61fd04b5dae0ee519c43d4c4 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 20 Mar 2017 15:57:48 -0400 Subject: [PATCH 1/5] [Encoder|Decoder]HttpMessageWriter server support There is a natural way to implement ServerHttpMessage[Reader|Writer] from [Encoder|Decoder]HttpMessageWriter by resolving hints first via a protected method and then delegating to the regular read or write. There is no downside either since it does not prevent [Encoder|Decoder]HttpMessageWriter from being used for both client and server scenarios while they're more useful. As a positive side effect AbstractServerHttpMessage[Reader|Writer] can be removed further simplfications can be made (in a future commit) to accept ServerHttpMessageWriter for configuration purposes on the server side and remove instanceof checks for ServerHttpMessageWriter. --- .../AbstractServerHttpMessageReader.java | 107 ------------------ .../AbstractServerHttpMessageWriter.java | 93 --------------- .../http/codec/DecoderHttpMessageReader.java | 40 ++++++- .../http/codec/EncoderHttpMessageWriter.java | 31 ++++- .../Jackson2ServerHttpMessageReader.java | 15 ++- .../Jackson2ServerHttpMessageWriter.java | 51 ++++----- .../config/WebFluxConfigurationSupport.java | 6 +- 7 files changed, 104 insertions(+), 239 deletions(-) delete mode 100644 spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageReader.java delete mode 100644 spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageWriter.java diff --git a/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageReader.java deleted file mode 100644 index 36cfb37732a..00000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageReader.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2002-2016 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.codec; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.MethodParameter; -import org.springframework.core.ResolvableType; -import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpInputMessage; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -/** - * {@link HttpMessageReader} wrapper that implements {@link ServerHttpMessageReader} in order - * to allow providing hints to the nested {@code reader} or setting the response status for - * example, by implementing {@link #resolveReadHints(ResolvableType, ResolvableType, ServerHttpRequest)}. - * - * @author Sebastien Deleuze - * @since 5.0 - */ -public abstract class AbstractServerHttpMessageReader implements ServerHttpMessageReader { - - private HttpMessageReader reader; - - - public AbstractServerHttpMessageReader(HttpMessageReader reader) { - this.reader = reader; - } - - @Override - public boolean canRead(ResolvableType elementType, MediaType mediaType) { - return this.reader.canRead(elementType, mediaType); - } - - @Override - public Flux read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map hints) { - return this.reader.read(elementType, inputMessage, hints); - } - - @Override - public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map hints) { - return this.reader.readMono(elementType, inputMessage, hints); - } - - @Override - public List getReadableMediaTypes() { - return this.reader.getReadableMediaTypes(); - } - - @Override - public Flux read(ResolvableType streamType, ResolvableType elementType, - ServerHttpRequest request, ServerHttpResponse response, Map hints) { - - Map mergedHints = new HashMap<>(hints); - mergedHints.putAll(resolveReadHints(streamType, elementType, request)); - - return (this.reader instanceof ServerHttpMessageReader ? - ((ServerHttpMessageReader)this.reader).read(streamType, elementType, request, response, mergedHints) : - this.read(elementType, request, mergedHints)); - } - - @Override - public Mono readMono(ResolvableType streamType, ResolvableType elementType, - ServerHttpRequest request, ServerHttpResponse response, Map hints) { - - Map mergedHints = new HashMap<>(hints); - mergedHints.putAll(resolveReadHints(streamType, elementType, request)); - - return (this.reader instanceof ServerHttpMessageReader ? - ((ServerHttpMessageReader)this.reader).readMono(streamType, elementType, request, response, mergedHints) : - this.readMono(elementType, request, mergedHints)); - } - - /** - * Invoked before reading the request to resolve hints by - * {@link #read(ResolvableType, ResolvableType, ServerHttpRequest, ServerHttpResponse, Map)}. - * - * @param streamType the original type used for the method return value. For annotation - * based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}. - * @param elementType the stream element type to process - * @param request the current HTTP request - * @return Additional information about how to write the body - */ - protected abstract Map resolveReadHints(ResolvableType streamType, - ResolvableType elementType, ServerHttpRequest request); - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageWriter.java deleted file mode 100644 index 54ddec8e504..00000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/AbstractServerHttpMessageWriter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2016 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.codec; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.core.MethodParameter; -import org.springframework.core.ResolvableType; -import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -/** - * {@link HttpMessageWriter} wrapper that implements {@link ServerHttpMessageWriter} in order - * to allow providing hints to the nested {@code writer} or setting the response status for - * example, by implementing {@link #resolveWriteHints(ResolvableType, ResolvableType, MediaType, ServerHttpRequest)}. - * - * @author Sebastien Deleuze - * @since 5.0 - */ -public abstract class AbstractServerHttpMessageWriter implements ServerHttpMessageWriter { - - private HttpMessageWriter writer; - - - public AbstractServerHttpMessageWriter(HttpMessageWriter writer) { - this.writer = writer; - } - - - @Override - public boolean canWrite(ResolvableType elementType, MediaType mediaType) { - return this.writer.canWrite(elementType, mediaType); - } - - @Override - public List getWritableMediaTypes() { - return this.writer.getWritableMediaTypes(); - } - - @Override - public Mono write(Publisher inputStream, ResolvableType elementType, - MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map hints) { - return this.writer.write(inputStream, elementType, mediaType, outputMessage, hints); - } - - @Override - public Mono write(Publisher inputStream, ResolvableType streamType, ResolvableType elementType, - MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { - - Map mergedHints = new HashMap<>(hints); - mergedHints.putAll(resolveWriteHints(streamType, elementType, mediaType, request)); - return (this.writer instanceof ServerHttpMessageWriter ? - ((ServerHttpMessageWriter)this.writer).write(inputStream, streamType, - elementType, mediaType, request, response, mergedHints) : - this.writer.write(inputStream, elementType, mediaType, response, mergedHints)); - } - - /** - * Invoked before writing the response to resolve hints by - * {@link #write(Publisher, ResolvableType, ResolvableType, MediaType, ServerHttpRequest, ServerHttpResponse, Map)}. - * @param streamType the original type used for the method return value. For annotation - * based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}. - * @param elementType the stream element type to process - * @param mediaType the content type to use when writing. May be {@code null} to - * indicate that the default content type of the converter must be used. - * @param request the current HTTP request - * @return additional information about how to write the body - */ - protected abstract Map resolveWriteHints(ResolvableType streamType, ResolvableType elementType, - MediaType mediaType, ServerHttpRequest request); - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index 962420b170a..e7812439b4c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -17,6 +17,7 @@ package org.springframework.http.codec; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,6 +29,8 @@ import org.springframework.core.codec.Decoder; import org.springframework.http.HttpMessage; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpInputMessage; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; /** @@ -38,7 +41,7 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 5.0 */ -public class DecoderHttpMessageReader implements HttpMessageReader { +public class DecoderHttpMessageReader implements ServerHttpMessageReader { private final Decoder decoder; @@ -94,4 +97,39 @@ public class DecoderHttpMessageReader implements HttpMessageReader { return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM); } + + // ServerHttpMessageReader... + + @Override + public Flux read(ResolvableType streamType, ResolvableType elementType, + ServerHttpRequest request, ServerHttpResponse response, Map hints) { + + Map allHints = new HashMap<>(4); + allHints.putAll(resolveReadHints(streamType, elementType, request, response)); + allHints.putAll(hints); + + return read(elementType, request, allHints); + } + + @Override + public Mono readMono(ResolvableType streamType, ResolvableType elementType, + ServerHttpRequest request, ServerHttpResponse response, Map hints) { + + Map allHints = new HashMap<>(4); + allHints.putAll(resolveReadHints(streamType, elementType, request, response)); + allHints.putAll(hints); + + return readMono(elementType, request, allHints); + } + + /** + * Resolve hints to pass to the decoder, e.g. by checking for annotations + * on a controller method parameter or checking the server request. + */ + protected Map resolveReadHints(ResolvableType streamType, + ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { + + return Collections.emptyMap(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index da6ac6513b3..3ae2f9ddacb 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -16,6 +16,8 @@ package org.springframework.http.codec; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +31,8 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; 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; import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT; @@ -42,7 +46,7 @@ import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AF * @author Rossen Stoyanchev * @since 5.0 */ -public class EncoderHttpMessageWriter implements HttpMessageWriter { +public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { private final Encoder encoder; @@ -119,4 +123,29 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { return main; } + + // ServerHttpMessageWriter... + + @Override + public Mono write(Publisher inputStream, ResolvableType streamType, + ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, + ServerHttpResponse response, Map hints) { + + Map allHints = new HashMap<>(); + allHints.putAll(resolveWriteHints(streamType, elementType, mediaType, request, response)); + allHints.putAll(hints); + + return write(inputStream, elementType, mediaType, response, allHints); + } + + /** + * Resolve hints to pass to the encoder, e.g. by checking for annotations + * on a controller method parameter or checking the server request. + */ + protected Map resolveWriteHints(ResolvableType streamType, ResolvableType elementType, + MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { + + return Collections.emptyMap(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java index eefad0e5574..50da50349a4 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -23,8 +23,10 @@ import com.fasterxml.jackson.annotation.JsonView; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.codec.Decoder; import org.springframework.http.codec.json.AbstractJackson2Codec; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; /** * {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints: @@ -33,18 +35,21 @@ import org.springframework.http.server.reactive.ServerHttpRequest; * * * @author Sebastien Deleuze + * @author Rossen Stoyanchev * @since 5.0 * @see com.fasterxml.jackson.annotation.JsonView */ -public class Jackson2ServerHttpMessageReader extends AbstractServerHttpMessageReader { +public class Jackson2ServerHttpMessageReader extends DecoderHttpMessageReader { - public Jackson2ServerHttpMessageReader(HttpMessageReader reader) { - super(reader); + + public Jackson2ServerHttpMessageReader(Decoder decoder) { + super(decoder); } + @Override protected Map resolveReadHints(ResolvableType streamType, - ResolvableType elementType, ServerHttpRequest request) { + ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { Object source = streamType.getSource(); MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null); diff --git a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java index a00d358a946..c7eedce36cf 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java @@ -44,38 +44,13 @@ import org.springframework.http.server.reactive.ServerHttpResponse; * @since 5.0 * @see com.fasterxml.jackson.annotation.JsonView */ -public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWriter { +public class Jackson2ServerHttpMessageWriter extends EncoderHttpMessageWriter { public Jackson2ServerHttpMessageWriter(Encoder encoder) { - super(new EncoderHttpMessageWriter<>(encoder)); + super(encoder); } - public Jackson2ServerHttpMessageWriter(HttpMessageWriter writer) { - super(writer); - } - - - @Override - protected Map resolveWriteHints(ResolvableType streamType, - ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) { - - Map hints = new HashMap<>(); - Object source = streamType.getSource(); - MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null); - if (returnValue != null) { - JsonView annotation = returnValue.getMethodAnnotation(JsonView.class); - if (annotation != null) { - Class[] classes = annotation.value(); - if (classes.length != 1) { - throw new IllegalArgumentException( - "@JsonView only supported for write hints with exactly 1 class argument: " + returnValue); - } - hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); - } - } - return hints; - } @Override public Mono write(Publisher inputStream, ResolvableType elementType, MediaType mediaType, @@ -100,4 +75,26 @@ public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWr } return super.write(inputStream, streamType, elementType, mediaType, request, response, hints); } + + @Override + protected Map resolveWriteHints(ResolvableType streamType, ResolvableType elementType, + MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { + + Map hints = new HashMap<>(); + Object source = streamType.getSource(); + MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null); + if (returnValue != null) { + JsonView annotation = returnValue.getMethodAnnotation(JsonView.class); + if (annotation != null) { + Class[] classes = annotation.value(); + if (classes.length != 1) { + throw new IllegalArgumentException( + "@JsonView only supported for write hints with exactly 1 class argument: " + returnValue); + } + hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); + } + } + return hints; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 112b5d6dc75..5d89ea0e265 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -491,12 +491,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(); writers.add(new Jackson2ServerHttpMessageWriter(encoder)); sseDataEncoders.add(encoder); - HttpMessageWriter writer = new ServerSentEventHttpMessageWriter(sseDataEncoders); - writers.add(new Jackson2ServerHttpMessageWriter(writer)); - } - else { - writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders)); } + writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders)); } /** From f65544c1928d9e4d9b491faabc1e712cd628774b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 20 Mar 2017 16:01:30 -0400 Subject: [PATCH 2/5] Introduce ServerHttp[Encoder|Decoder] ServerHttpEncoder and ServerHttpDecoder are HTTP-specific specializations that can prepare encoding and decoding hints from extra information available on the server side. As a result Jackson2ServerHttpMessageReader is no longer needed. --- .../http/codec/DecoderHttpMessageReader.java | 21 ++++-- .../http/codec/EncoderHttpMessageWriter.java | 19 +++-- .../Jackson2ServerHttpMessageReader.java | 70 ------------------- .../Jackson2ServerHttpMessageWriter.java | 29 +------- .../http/codec/ServerHttpDecoder.java | 51 ++++++++++++++ .../http/codec/ServerHttpEncoder.java | 53 ++++++++++++++ .../ServerSentEventHttpMessageWriter.java | 23 +++++- .../http/codec/json/Jackson2JsonDecoder.java | 31 +++++++- .../http/codec/json/Jackson2JsonEncoder.java | 37 +++++++++- .../config/WebFluxConfigurationSupport.java | 4 +- 10 files changed, 222 insertions(+), 116 deletions(-) delete mode 100644 spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java create mode 100644 spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java create mode 100644 spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index e7812439b4c..cd104d41cb5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -34,7 +34,11 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; /** - * Implementation of {@code HttpMessageReader} delegating to a {@link Decoder}. + * {@code HttpMessageReader} that wraps and delegates to a {@link Decoder}. + * + *

Also a {@code ServerHttpMessageReader} that pre-resolves decoding hints + * from the extra information available on the server side such as the request + * or controller method parameter annotations. * * @author Arjen Poutsma * @author Sebastien Deleuze @@ -105,7 +109,7 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(4); - allHints.putAll(resolveReadHints(streamType, elementType, request, response)); + allHints.putAll(getReadHints(streamType, elementType, request, response)); allHints.putAll(hints); return read(elementType, request, allHints); @@ -116,19 +120,24 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(4); - allHints.putAll(resolveReadHints(streamType, elementType, request, response)); + allHints.putAll(getReadHints(streamType, elementType, request, response)); allHints.putAll(hints); return readMono(elementType, request, allHints); } /** - * Resolve hints to pass to the decoder, e.g. by checking for annotations - * on a controller method parameter or checking the server request. + * Get additional hints for decoding for example based on the server request + * or annotations from controller method parameters. By default, delegate to + * the decoder if it is an instance of {@link ServerHttpDecoder}. */ - protected Map resolveReadHints(ResolvableType streamType, + protected Map getReadHints(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { + if (this.decoder instanceof ServerHttpDecoder) { + ServerHttpDecoder httpDecoder = (ServerHttpDecoder) this.decoder; + return httpDecoder.getDecodeHints(streamType, elementType, request, response); + } return Collections.emptyMap(); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 3ae2f9ddacb..9dbfe37f8f2 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -39,7 +39,11 @@ import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_H import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AFTER_EACH_ELEMENT; /** - * Implementation of {@code HttpMessageWriter} delegating to an {@link Encoder}. + * {@code HttpMessageWriter} that wraps and delegates to a {@link Encoder}. + * + *

Also a {@code ServerHttpMessageWriter} that pre-resolves encoding hints + * from the extra information available on the server side such as the request + * or controller method annotations. * * @author Arjen Poutsma * @author Sebastien Deleuze @@ -132,19 +136,24 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(); - allHints.putAll(resolveWriteHints(streamType, elementType, mediaType, request, response)); + allHints.putAll(getWriteHints(streamType, elementType, mediaType, request, response)); allHints.putAll(hints); return write(inputStream, elementType, mediaType, response, allHints); } /** - * Resolve hints to pass to the encoder, e.g. by checking for annotations - * on a controller method parameter or checking the server request. + * Get additional hints for encoding for example based on the server request + * or annotations from controller method parameters. By default, delegate to + * the encoder if it is an instance of {@link ServerHttpEncoder}. */ - protected Map resolveWriteHints(ResolvableType streamType, ResolvableType elementType, + protected Map getWriteHints(ResolvableType streamType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { + if (this.encoder instanceof ServerHttpEncoder) { + ServerHttpEncoder httpEncoder = (ServerHttpEncoder) this.encoder; + return httpEncoder.getEncodeHints(streamType, elementType, mediaType, request, response); + } return Collections.emptyMap(); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java deleted file mode 100644 index 50da50349a4..00000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2017 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.codec; - -import java.util.Collections; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonView; - -import org.springframework.core.MethodParameter; -import org.springframework.core.ResolvableType; -import org.springframework.core.codec.Decoder; -import org.springframework.http.codec.json.AbstractJackson2Codec; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; - -/** - * {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints: - *

    - *
  • {@code @JsonView} + {@code @RequestBody} annotated handler method parameter
  • - *
- * - * @author Sebastien Deleuze - * @author Rossen Stoyanchev - * @since 5.0 - * @see com.fasterxml.jackson.annotation.JsonView - */ -public class Jackson2ServerHttpMessageReader extends DecoderHttpMessageReader { - - - public Jackson2ServerHttpMessageReader(Decoder decoder) { - super(decoder); - } - - - @Override - protected Map resolveReadHints(ResolvableType streamType, - ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) { - - Object source = streamType.getSource(); - MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null); - if (parameter != null) { - JsonView annotation = parameter.getParameterAnnotation(JsonView.class); - if (annotation != null) { - Class[] classes = annotation.value(); - if (classes.length != 1) { - throw new IllegalArgumentException( - "@JsonView only supported for read hints with exactly 1 class argument: " + parameter); - } - return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); - } - } - return Collections.emptyMap(); - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java index c7eedce36cf..e06730195e0 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java @@ -20,22 +20,20 @@ package org.springframework.http.codec; import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.annotation.JsonView; import org.reactivestreams.Publisher; -import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT; -import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AFTER_EACH_ELEMENT; import reactor.core.publisher.Mono; -import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.codec.AbstractEncoder; import org.springframework.core.codec.Encoder; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.codec.json.AbstractJackson2Codec; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; +import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT; +import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AFTER_EACH_ELEMENT; + /** * Jackson {@link ServerHttpMessageWriter} that resolves {@code @JsonView} annotated handler * method and deals with {@link AbstractEncoder#FLUSHING_STRATEGY_HINT}. @@ -76,25 +74,4 @@ public class Jackson2ServerHttpMessageWriter extends EncoderHttpMessageWriter resolveWriteHints(ResolvableType streamType, ResolvableType elementType, - MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { - - Map hints = new HashMap<>(); - Object source = streamType.getSource(); - MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null); - if (returnValue != null) { - JsonView annotation = returnValue.getMethodAnnotation(JsonView.class); - if (annotation != null) { - Class[] classes = annotation.value(); - if (classes.length != 1) { - throw new IllegalArgumentException( - "@JsonView only supported for write hints with exactly 1 class argument: " + returnValue); - } - hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); - } - } - return hints; - } - } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java new file mode 100644 index 00000000000..fc256c9bd93 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.codec; + +import java.util.Map; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.Decoder; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; + +/** + * {@code Decoder} extension for server-side decoding of the HTTP request body. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public interface ServerHttpDecoder extends Decoder { + + + /** + * Get decoding hints based on the server request or annotations on the + * target controller method parameter. + * + * @param actualType the actual target type to decode to, possibly a reactive + * wrapper and sourced from {@link org.springframework.core.MethodParameter}, + * i.e. providing access to method parameter annotations. + * @param elementType the element type within {@code Flux/Mono} that we're + * trying to decode to. + * @param request the current request + * @param response the current response + * @return a Map with hints, possibly empty + */ + Map getDecodeHints(ResolvableType actualType, ResolvableType elementType, + ServerHttpRequest request, ServerHttpResponse response); + +} diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java new file mode 100644 index 00000000000..20bc06583fc --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.codec; + +import java.util.Map; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.Encoder; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; + + +/** + * {@code Encoder} extension for server-side encoding of the HTTP response body. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public interface ServerHttpEncoder extends Encoder { + + + /** + * Get decoding hints based on the server request or annotations on the + * target controller method parameter. + * + * @param actualType the actual source type to encode, possibly a reactive + * wrapper and sourced from {@link org.springframework.core.MethodParameter}, + * i.e. providing access to method annotations. + * @param elementType the element type within {@code Flux/Mono} that we're + * trying to encode. + * @param request the current request + * @param response the current response + * @return a Map with hints, possibly empty + */ + Map getEncodeHints(ResolvableType actualType, ResolvableType elementType, + MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response); + +} diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java index a7ca7be6fd0..7f2032b137a 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java @@ -35,6 +35,8 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; 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; import org.springframework.util.MimeTypeUtils; @@ -47,7 +49,7 @@ import org.springframework.util.MimeTypeUtils; * @author Arjen Poutsma * @since 5.0 */ -public class ServerSentEventHttpMessageWriter implements HttpMessageWriter { +public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter { /** * Server-Sent Events hint key expecting a {@link Boolean} value which when set to true @@ -161,4 +163,23 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter write(Publisher inputStream, ResolvableType streamType, ResolvableType elementType, + MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, + Map hints) { + + Map allHints = this.dataEncoders.stream() + .filter(encoder -> encoder instanceof ServerHttpEncoder) + .map(encoder -> (ServerHttpEncoder) encoder) + .map(encoder -> encoder.getEncodeHints(streamType, elementType, mediaType, request, response)) + .reduce(new HashMap<>(), (t, u) -> { + t.putAll(u); + return t; + }); + + allHints.putAll(hints); + + return write(inputStream, elementType, mediaType, response, allHints); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java index bfb75adcda1..6ea5c90a397 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java @@ -17,9 +17,11 @@ package org.springframework.http.codec.json; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -30,10 +32,12 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.codec.CodecException; -import org.springframework.core.codec.Decoder; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.codec.ServerHttpDecoder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.MimeType; @@ -45,7 +49,7 @@ import org.springframework.util.MimeType; * @since 5.0 * @see Jackson2JsonEncoder */ -public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Decoder { +public class Jackson2JsonDecoder extends AbstractJackson2Codec implements ServerHttpDecoder { private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true); @@ -123,4 +127,27 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Decode }); } + + // ServerHttpDecoder... + + @Override + public Map getDecodeHints(ResolvableType actualType, ResolvableType elementType, + ServerHttpRequest request, ServerHttpResponse response) { + + Object source = actualType.getSource(); + MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null); + if (parameter != null) { + JsonView annotation = parameter.getParameterAnnotation(JsonView.class); + if (annotation != null) { + Class[] classes = annotation.value(); + if (classes.length != 1) { + throw new IllegalArgumentException( + "@JsonView only supported for read hints with exactly 1 class argument: " + parameter); + } + return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); + } + } + return Collections.emptyMap(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java index 2acede40f13..65cba1dad1d 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java @@ -18,9 +18,11 @@ package org.springframework.http.codec.json; import java.io.IOException; import java.io.OutputStream; +import java.util.HashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.core.PrettyPrinter; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; @@ -31,20 +33,25 @@ import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.type.TypeFactory; import org.reactivestreams.Publisher; -import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.MethodParameter; 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; +import org.springframework.http.MediaType; +import org.springframework.http.codec.ServerHttpEncoder; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.MimeType; +import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON; + /** * Encode from an {@code Object} stream to a byte stream of JSON objects, * using Jackson 2.6+. @@ -54,7 +61,7 @@ import org.springframework.util.MimeType; * @since 5.0 * @see Jackson2JsonDecoder */ -public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Encoder { +public class Jackson2JsonEncoder extends AbstractJackson2Codec implements ServerHttpEncoder { private final PrettyPrinter ssePrettyPrinter; @@ -144,4 +151,28 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Encode return buffer; } + + // ServerHttpEncoder... + + @Override + public Map getEncodeHints(ResolvableType actualType, ResolvableType elementType, + MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) { + + Map hints = new HashMap<>(); + Object source = actualType.getSource(); + MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null); + if (returnValue != null) { + JsonView annotation = returnValue.getMethodAnnotation(JsonView.class); + if (annotation != null) { + Class[] classes = annotation.value(); + if (classes.length != 1) { + throw new IllegalArgumentException( + "@JsonView only supported for write hints with exactly 1 class argument: " + returnValue); + } + hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]); + } + } + return hints; + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 5d89ea0e265..042d27271da 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -50,7 +50,6 @@ 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.Jackson2ServerHttpMessageReader; import org.springframework.http.codec.Jackson2ServerHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; @@ -339,8 +338,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); } if (jackson2Present) { - readers.add(new Jackson2ServerHttpMessageReader( - new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()))); + readers.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); } } From c8671041f124a95105e0a0d0752adabc71e91c6c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 20 Mar 2017 16:39:03 -0400 Subject: [PATCH 3/5] EncoderHttpMessageWriter supports streaming MediaTypes Support for flushing in EncoderHttpMessageWriter is now driven from a configurable list of "streaming" media types with the list including "application/stream+json" by default. As a result Jackson2ServerHttpMessageWriter is no longer needed. --- .../core/codec/AbstractEncoder.java | 7 -- .../http/codec/EncoderHttpMessageWriter.java | 41 ++++++++-- .../Jackson2ServerHttpMessageWriter.java | 77 ------------------- .../config/WebFluxConfigurationSupport.java | 3 +- .../DefaultHandlerStrategiesBuilder.java | 3 +- 5 files changed, 38 insertions(+), 93 deletions(-) delete mode 100644 spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java diff --git a/spring-core/src/main/java/org/springframework/core/codec/AbstractEncoder.java b/spring-core/src/main/java/org/springframework/core/codec/AbstractEncoder.java index d469db5d363..5313c3a3540 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/AbstractEncoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/AbstractEncoder.java @@ -31,13 +31,6 @@ import org.springframework.util.MimeType; */ public abstract class AbstractEncoder implements Encoder { - /** - * Hint key to use with a {@link FlushingStrategy} value. - */ - public static final String FLUSHING_STRATEGY_HINT = AbstractEncoder.class.getName() + ".flushingStrategy"; - - public enum FlushingStrategy { AUTO, AFTER_EACH_ELEMENT } - private final List encodableMimeTypes; diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 9dbfe37f8f2..0a9dfa6166c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -16,6 +16,7 @@ package org.springframework.http.codec; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -35,9 +36,6 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; -import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT; -import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AFTER_EACH_ELEMENT; - /** * {@code HttpMessageWriter} that wraps and delegates to a {@link Encoder}. * @@ -52,12 +50,22 @@ import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AF */ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { + /** + * Default list of media types that signify a "streaming" scenario such that + * there may be a time lag between items written and hence requires flushing. + */ + public static final List DEFAULT_STREAMING_MEDIA_TYPES = + Collections.singletonList(MediaType.APPLICATION_STREAM_JSON); + + private final Encoder encoder; private final List mediaTypes; private final MediaType defaultMediaType; + private final List streamingMediaTypes = new ArrayList<>(1); + /** * Create an instance wrapping the given {@link Encoder}. @@ -67,6 +75,7 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { this.encoder = encoder; this.mediaTypes = MediaType.asMediaTypes(encoder.getEncodableMimeTypes()); this.defaultMediaType = initDefaultMediaType(this.mediaTypes); + this.streamingMediaTypes.addAll(DEFAULT_STREAMING_MEDIA_TYPES); } private static MediaType initDefaultMediaType(List mediaTypes) { @@ -86,6 +95,23 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { return this.mediaTypes; } + /** + * Configure "streaming" media types for which flushing should be performed + * automatically vs at the end of the input stream. + *

By default this is set to {@link #DEFAULT_STREAMING_MEDIA_TYPES}. + * @param mediaTypes one or more media types to add to the list + */ + public void setStreamingMediaTypes(List mediaTypes) { + this.streamingMediaTypes.addAll(mediaTypes); + } + + /** + * Return the configured list of "streaming" media types. + */ + public List getStreamingMediaTypes() { + return Collections.unmodifiableList(this.streamingMediaTypes); + } + @Override public boolean canWrite(ResolvableType elementType, MediaType mediaType) { @@ -111,8 +137,9 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { Flux body = this.encoder.encode(inputStream, outputMessage.bufferFactory(), elementType, headers.getContentType(), hints); - return (hints.get(FLUSHING_STRATEGY_HINT) == AFTER_EACH_ELEMENT ? - outputMessage.writeAndFlushWith(body.map(Flux::just)) : outputMessage.writeWith(body)); + return isStreamingMediaType(headers.getContentType()) ? + outputMessage.writeAndFlushWith(body.map(Flux::just)) : + outputMessage.writeWith(body); } private static boolean useFallback(MediaType main, MediaType fallback) { @@ -127,6 +154,10 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { return main; } + private boolean isStreamingMediaType(MediaType contentType) { + return this.streamingMediaTypes.stream().anyMatch(contentType::isCompatibleWith); + } + // ServerHttpMessageWriter... diff --git a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java deleted file mode 100644 index e06730195e0..00000000000 --- a/spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2016 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.codec; - - -import java.util.HashMap; -import java.util.Map; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.core.ResolvableType; -import org.springframework.core.codec.AbstractEncoder; -import org.springframework.core.codec.Encoder; -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 static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT; -import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AFTER_EACH_ELEMENT; - -/** - * Jackson {@link ServerHttpMessageWriter} that resolves {@code @JsonView} annotated handler - * method and deals with {@link AbstractEncoder#FLUSHING_STRATEGY_HINT}. - * - * @author Sebastien Deleuze - * @since 5.0 - * @see com.fasterxml.jackson.annotation.JsonView - */ -public class Jackson2ServerHttpMessageWriter extends EncoderHttpMessageWriter { - - - public Jackson2ServerHttpMessageWriter(Encoder encoder) { - super(encoder); - } - - - @Override - public Mono write(Publisher inputStream, ResolvableType elementType, MediaType mediaType, - ReactiveHttpOutputMessage outputMessage, Map hints) { - - if ((mediaType != null) && mediaType.isCompatibleWith(MediaType.APPLICATION_STREAM_JSON)) { - Map hintsWithFlush = new HashMap<>(hints); - hintsWithFlush.put(FLUSHING_STRATEGY_HINT, AFTER_EACH_ELEMENT); - return super.write(inputStream, elementType, mediaType, outputMessage, hintsWithFlush); - } - return super.write(inputStream, elementType, mediaType, outputMessage, hints); - } - - @Override - public Mono write(Publisher inputStream, ResolvableType streamType, ResolvableType elementType, - MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { - - if ((mediaType != null) && mediaType.isCompatibleWith(MediaType.APPLICATION_STREAM_JSON)) { - Map hintsWithFlush = new HashMap<>(hints); - hintsWithFlush.put(FLUSHING_STRATEGY_HINT, AFTER_EACH_ELEMENT); - return super.write(inputStream, streamType, elementType, mediaType, request, response, hintsWithFlush); - } - return super.write(inputStream, streamType, elementType, mediaType, request, response, hints); - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 042d27271da..56e19e803fd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -50,7 +50,6 @@ 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.Jackson2ServerHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; @@ -487,7 +486,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { } if (jackson2Present) { Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(); - writers.add(new Jackson2ServerHttpMessageWriter(encoder)); + writers.add(new EncoderHttpMessageWriter<>(encoder)); sseDataEncoders.add(encoder); } writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders)); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java index ae3a83f4945..378687ca53e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultHandlerStrategiesBuilder.java @@ -37,7 +37,6 @@ import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.FormHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.codec.Jackson2ServerHttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; @@ -99,7 +98,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder { if (jackson2Present) { messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder(); - messageWriter(new Jackson2ServerHttpMessageWriter(jsonEncoder)); + messageWriter(new EncoderHttpMessageWriter<>(jsonEncoder)); messageWriter( new ServerSentEventHttpMessageWriter(Collections.singletonList(jsonEncoder))); } From 54013a092018920651dc69f8228f443777d4d81b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 20 Mar 2017 21:48:52 -0400 Subject: [PATCH 4/5] @EnableWebFlux uses ServerHttpMessage[Reader|Writer] ServerHttpMessage[Reader|Writer] are now expected for WebFlux, annotated controller endpoint and subsequently the instanceof checks for HttpMessage[Reader|Writer] vs ServerHttpMessage[Reader|Writer] can be removed from AbstractMessageReaderArgumentResolver and AbtractMessageWriterResultHandler. --- .../DelegatingWebFluxConfiguration.java | 12 +++---- .../config/WebFluxConfigurationSupport.java | 24 +++++++------- .../reactive/config/WebFluxConfigurer.java | 12 +++---- .../config/WebFluxConfigurerComposite.java | 12 +++---- ...AbstractMessageReaderArgumentResolver.java | 33 +++++-------------- .../AbstractMessageWriterResultHandler.java | 24 ++++++-------- .../HttpEntityArgumentResolver.java | 6 ++-- .../RequestBodyArgumentResolver.java | 6 ++-- .../RequestMappingHandlerAdapter.java | 8 ++--- .../annotation/ResponseBodyResultHandler.java | 8 ++--- .../ResponseEntityResultHandler.java | 6 ++-- .../DelegatingWebFluxConfigurationTests.java | 8 ++--- .../WebFluxConfigurationSupportTests.java | 24 +++++++------- .../DispatcherHandlerIntegrationTests.java | 11 ++++--- .../HttpEntityArgumentResolverTests.java | 4 +-- .../MessageReaderArgumentResolverTests.java | 6 ++-- .../MessageWriterResultHandlerTests.java | 8 ++--- .../RequestBodyArgumentResolverTests.java | 4 +-- .../ResponseBodyResultHandlerTests.java | 4 +-- .../ResponseEntityResultHandlerTests.java | 6 ++-- 20 files changed, 106 insertions(+), 120 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java index ba632e5ff39..18cb061f3e5 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java @@ -21,8 +21,8 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.util.CollectionUtils; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -76,12 +76,12 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport } @Override - protected void configureMessageReaders(List> messageReaders) { + protected void configureMessageReaders(List> messageReaders) { this.configurers.configureMessageReaders(messageReaders); } @Override - protected void extendMessageReaders(List> messageReaders) { + protected void extendMessageReaders(List> messageReaders) { this.configurers.extendMessageReaders(messageReaders); } @@ -101,12 +101,12 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport } @Override - protected void configureMessageWriters(List> messageWriters) { + protected void configureMessageWriters(List> messageWriters) { this.configurers.configureMessageWriters(messageWriters); } @Override - protected void extendMessageWriters(List> messageWriters) { + protected void extendMessageWriters(List> messageWriters) { this.configurers.extendMessageWriters(messageWriters); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 56e19e803fd..8916dd71574 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -48,9 +48,9 @@ import org.springframework.format.support.FormattingConversionService; import org.springframework.http.MediaType; 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.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.codec.ServerSentEventHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; @@ -104,9 +104,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { private PathMatchConfigurer pathMatchConfigurer; - private List> messageReaders; + private List> messageReaders; - private List> messageWriters; + private List> messageWriters; private ApplicationContext applicationContext; @@ -299,7 +299,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { *

Use {@link #configureMessageReaders} to configure the list or * {@link #extendMessageReaders} to add in addition to the default ones. */ - protected final List> getMessageReaders() { + protected final List> getMessageReaders() { if (this.messageReaders == null) { this.messageReaders = new ArrayList<>(); configureMessageReaders(this.messageReaders); @@ -318,7 +318,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { * {@link #addDefaultHttpMessageReaders}. * @param messageReaders a list to add message readers to, initially an empty */ - protected void configureMessageReaders(List> messageReaders) { + protected void configureMessageReaders(List> messageReaders) { } /** @@ -327,7 +327,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { * {@code ByteBuffer}, {@code String}, {@code Resource}, JAXB2, and Jackson * (if present on the classpath). */ - protected final void addDefaultHttpMessageReaders(List> readers) { + protected final void addDefaultHttpMessageReaders(List> readers) { readers.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder())); readers.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); readers.add(new DecoderHttpMessageReader<>(new DataBufferDecoder())); @@ -345,7 +345,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { * Override this to modify the list of message readers after it has been * configured, for example to add some in addition to the default ones. */ - protected void extendMessageReaders(List> messageReaders) { + protected void extendMessageReaders(List> messageReaders) { } /** @@ -450,7 +450,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { *

Use {@link #configureMessageWriters(List)} to configure the list or * {@link #extendMessageWriters(List)} to add in addition to the default ones. */ - protected final List> getMessageWriters() { + protected final List> getMessageWriters() { if (this.messageWriters == null) { this.messageWriters = new ArrayList<>(); configureMessageWriters(this.messageWriters); @@ -468,13 +468,13 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { * {@link #addDefaultHttpMessageWriters}. * @param messageWriters a list to add message writers to, initially an empty */ - protected void configureMessageWriters(List> messageWriters) { + protected void configureMessageWriters(List> messageWriters) { } /** * Adds default converters that sub-classes can call from * {@link #configureMessageWriters(List)}. */ - protected final void addDefaultHttpMessageWriters(List> writers) { + protected final void addDefaultHttpMessageWriters(List> writers) { List> sseDataEncoders = new ArrayList<>(); writers.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder())); writers.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); @@ -496,7 +496,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { * Override this to modify the list of message writers after it has been * configured, for example to add some in addition to the default ones. */ - protected void extendMessageWriters(List> messageWriters) { + protected void extendMessageWriters(List> messageWriters) { } @Bean diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java index 0e9b024ea37..fdf29d8f540 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java @@ -22,8 +22,8 @@ import java.util.Optional; import org.springframework.core.convert.converter.Converter; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.CompositeContentTypeResolver; @@ -97,14 +97,14 @@ public interface WebFluxConfigurer { * in addition to the default ones. * @param readers an empty list to add message readers to */ - default void configureMessageReaders(List> readers) { + default void configureMessageReaders(List> readers) { } /** * An alternative to {@link #configureMessageReaders(List)} that allows * modifying the message readers to use after default ones have been added. */ - default void extendMessageReaders(List> readers) { + default void extendMessageReaders(List> readers) { } /** @@ -141,14 +141,14 @@ public interface WebFluxConfigurer { * in addition to the default ones. * @param writers a empty list to add message writers to */ - default void configureMessageWriters(List> writers) { + default void configureMessageWriters(List> writers) { } /** * An alternative to {@link #configureMessageWriters(List)} that allows * modifying the message writers to use after default ones have been added. */ - default void extendMessageWriters(List> writers) { + default void extendMessageWriters(List> writers) { } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java index 618150e42f2..51070f12c01 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java @@ -23,8 +23,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.util.CollectionUtils; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; @@ -76,12 +76,12 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer { } @Override - public void configureMessageReaders(List> readers) { + public void configureMessageReaders(List> readers) { this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(readers)); } @Override - public void extendMessageReaders(List> readers) { + public void extendMessageReaders(List> readers) { this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(readers)); } @@ -101,12 +101,12 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer { } @Override - public void configureMessageWriters(List> writers) { + public void configureMessageWriters(List> writers) { this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(writers)); } @Override - public void extendMessageWriters(List> writers) { + public void extendMessageWriters(List> writers) { this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(writers)); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index afef435f19e..57587d28fc0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -32,7 +32,6 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -49,7 +48,7 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; /** * Abstract base class for argument resolvers that resolve method arguments - * by reading the request body with an {@link HttpMessageReader}. + * by reading the request body with an {@link ServerHttpMessageReader}. * *

Applies validation if the method argument is annotated with * {@code @javax.validation.Valid} or @@ -61,16 +60,16 @@ import org.springframework.web.server.UnsupportedMediaTypeStatusException; */ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMethodArgumentResolverSupport { - private final List> messageReaders; + private final List> messageReaders; private final List supportedMediaTypes; /** - * Constructor with {@link HttpMessageReader}'s and a {@link Validator}. + * Constructor with {@link ServerHttpMessageReader}'s and a {@link Validator}. * @param readers readers to convert from the request body */ - protected AbstractMessageReaderArgumentResolver(List> readers) { + protected AbstractMessageReaderArgumentResolver(List> readers) { this(readers, new ReactiveAdapterRegistry()); } @@ -79,7 +78,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho * @param messageReaders readers to convert from the request body * @param adapterRegistry for adapting to other reactive types from Flux and Mono */ - protected AbstractMessageReaderArgumentResolver(List> messageReaders, + protected AbstractMessageReaderArgumentResolver(List> messageReaders, ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -95,7 +94,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho /** * Return the configured message converters. */ - public List> getMessageReaders() { + public List> getMessageReaders() { return this.messageReaders; } @@ -114,19 +113,12 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho mediaType = MediaType.APPLICATION_OCTET_STREAM; } - for (HttpMessageReader reader : getMessageReaders()) { + for (ServerHttpMessageReader reader : getMessageReaders()) { if (reader.canRead(elementType, mediaType)) { Map readHints = Collections.emptyMap(); if (adapter != null && adapter.isMultiValue()) { - Flux flux; - if (reader instanceof ServerHttpMessageReader) { - ServerHttpMessageReader serverReader = ((ServerHttpMessageReader) reader); - flux = serverReader.read(bodyType, elementType, request, response, readHints); - } - else { - flux = reader.read(elementType, request, readHints); - } + Flux flux = reader.read(bodyType, elementType, request, response, readHints); flux = flux.onErrorResumeWith(ex -> Flux.error(getReadError(bodyParameter, ex))); if (isBodyRequired || !adapter.supportsEmpty()) { flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter))); @@ -139,14 +131,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho return Mono.just(adapter.fromPublisher(flux)); } else { - Mono mono; - if (reader instanceof ServerHttpMessageReader) { - ServerHttpMessageReader serverReader = (ServerHttpMessageReader) reader; - mono = serverReader.readMono(bodyType, elementType, request, response, readHints); - } - else { - mono = reader.readMono(elementType, request, readHints); - } + Mono mono = reader.readMono(bodyType, elementType, request, response, readHints); mono = mono.otherwise(ex -> Mono.error(getReadError(bodyParameter, ex))); if (isBodyRequired || (adapter != null && !adapter.supportsEmpty())) { mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter))); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java index 74bab5f0d26..ba3a7ee588c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageWriterResultHandler.java @@ -27,7 +27,6 @@ import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.http.MediaType; -import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -39,24 +38,24 @@ import org.springframework.web.server.ServerWebExchange; /** * Abstract base class for result handlers that handle return values by writing - * to the response with {@link HttpMessageWriter}. + * to the response with {@link ServerHttpMessageWriter}. * * @author Rossen Stoyanchev * @since 5.0 */ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHandlerSupport { - private final List> messageWriters; + private final List> messageWriters; /** - * Constructor with {@link HttpMessageWriter}s and a + * Constructor with {@link ServerHttpMessageWriter}s and a * {@code RequestedContentTypeResolver}. * * @param messageWriters for serializing Objects to the response body stream * @param contentTypeResolver for resolving the requested content type */ - protected AbstractMessageWriterResultHandler(List> messageWriters, + protected AbstractMessageWriterResultHandler(List> messageWriters, RequestedContentTypeResolver contentTypeResolver) { super(contentTypeResolver); @@ -72,7 +71,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa * @param adapterRegistry for adapting other reactive types (e.g. rx.Observable, * rx.Single, etc.) to Flux or Mono */ - protected AbstractMessageWriterResultHandler(List> messageWriters, + protected AbstractMessageWriterResultHandler(List> messageWriters, RequestedContentTypeResolver contentTypeResolver, ReactiveAdapterRegistry adapterRegistry) { @@ -85,7 +84,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa /** * Return the configured message converters. */ - public List> getMessageWriters() { + public List> getMessageWriters() { return this.messageWriters; } @@ -116,13 +115,10 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa ServerHttpResponse response = exchange.getResponse(); MediaType bestMediaType = selectMediaType(exchange, () -> getProducibleMediaTypes(elementType)); if (bestMediaType != null) { - for (HttpMessageWriter messageWriter : getMessageWriters()) { - if (messageWriter.canWrite(elementType, bestMediaType)) { - return (messageWriter instanceof ServerHttpMessageWriter ? - ((ServerHttpMessageWriter) messageWriter).write((Publisher) publisher, - bodyType, elementType, bestMediaType, request, response, Collections.emptyMap()) : - messageWriter.write((Publisher) publisher, elementType, - bestMediaType, response, Collections.emptyMap())); + for (ServerHttpMessageWriter writer : getMessageWriters()) { + if (writer.canWrite(elementType, bestMediaType)) { + return writer.write((Publisher) publisher, bodyType, elementType, + bestMediaType, request, response, Collections.emptyMap()); } } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java index d4e211f2481..41cfd15c73b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java @@ -24,7 +24,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.http.HttpEntity; import org.springframework.http.RequestEntity; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; @@ -42,7 +42,9 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes implements HandlerMethodArgumentResolver { - public HttpEntityArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { + public HttpEntityArgumentResolver(List> readers, + ReactiveAdapterRegistry registry) { + super(readers, registry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java index 6084a88a0f8..c512fb2403e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java @@ -22,7 +22,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; @@ -47,7 +47,9 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe implements HandlerMethodArgumentResolver { - public RequestBodyArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { + public RequestBodyArgumentResolver(List> readers, + ReactiveAdapterRegistry registry) { + super(readers, registry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java index d35a8256733..5a4a95f44a6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java @@ -44,7 +44,7 @@ import org.springframework.core.codec.ByteBufferDecoder; import org.springframework.core.codec.DataBufferDecoder; import org.springframework.core.codec.StringDecoder; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.InitBinder; @@ -76,7 +76,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class); - private final List> messageReaders = new ArrayList<>(10); + private final List> messageReaders = new ArrayList<>(10); private WebBindingInitializer webBindingInitializer; @@ -124,7 +124,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application /** * Configure message readers to de-serialize the request body with. */ - public void setMessageReaders(List> messageReaders) { + public void setMessageReaders(List> messageReaders) { this.messageReaders.clear(); this.messageReaders.addAll(messageReaders); } @@ -132,7 +132,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application /** * Return the configured message readers. */ - public List> getMessageReaders() { + public List> getMessageReaders() { return this.messageReaders; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java index d0ccd3c65b8..cc9a6d5c65e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java @@ -23,7 +23,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; @@ -34,7 +34,7 @@ import org.springframework.web.server.ServerWebExchange; /** * {@code HandlerResultHandler} that handles return values from methods annotated * with {@code @ResponseBody} writing to the body of the request or response with - * an {@link HttpMessageWriter}. + * an {@link ServerHttpMessageWriter}. * *

By default the order for this result handler is set to 100. As it detects * the presence of {@code @ResponseBody} it should be ordered after result @@ -56,7 +56,7 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle * @param writers writers for serializing to the response body * @param resolver to determine the requested content type */ - public ResponseBodyResultHandler(List> writers, + public ResponseBodyResultHandler(List> writers, RequestedContentTypeResolver resolver) { this(writers, resolver, new ReactiveAdapterRegistry()); @@ -68,7 +68,7 @@ public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandle * @param resolver to determine the requested content type * @param registry for adaptation to reactive types */ - public ResponseBodyResultHandler(List> writers, + public ResponseBodyResultHandler(List> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) { super(writers, resolver, registry); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java index 6470f5d1d60..30bef89cc41 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java @@ -30,7 +30,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.util.Assert; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResultHandler; @@ -57,7 +57,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand * @param writers writers for serializing to the response body * @param resolver to determine the requested content type */ - public ResponseEntityResultHandler(List> writers, + public ResponseEntityResultHandler(List> writers, RequestedContentTypeResolver resolver) { this(writers, resolver, new ReactiveAdapterRegistry()); @@ -69,7 +69,7 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand * @param resolver to determine the requested content type * @param registry for adaptation to reactive types */ - public ResponseEntityResultHandler(List> writers, + public ResponseEntityResultHandler(List> writers, RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) { super(writers, resolver, registry); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java index d419578c66d..dc1fbc14944 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java @@ -30,8 +30,8 @@ import org.mockito.MockitoAnnotations; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.format.FormatterRegistry; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; @@ -58,10 +58,10 @@ public class DelegatingWebFluxConfigurationTests { private WebFluxConfigurer webFluxConfigurer; @Captor - private ArgumentCaptor>> readers; + private ArgumentCaptor>> readers; @Captor - private ArgumentCaptor>> writers; + private ArgumentCaptor>> writers; @Captor private ArgumentCaptor formatterRegistry; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java index 004f66b0726..e6f7a274851 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/WebFluxConfigurationSupportTests.java @@ -36,8 +36,8 @@ import org.springframework.core.io.Resource; import org.springframework.http.MediaType; 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.ServerHttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; @@ -127,7 +127,7 @@ public class WebFluxConfigurationSupportTests { RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); - List> readers = adapter.getMessageReaders(); + List> readers = adapter.getMessageReaders(); assertEquals(7, readers.size()); assertHasMessageReader(readers, byte[].class, APPLICATION_OCTET_STREAM); @@ -160,7 +160,7 @@ public class WebFluxConfigurationSupportTests { RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); - List> messageReaders = adapter.getMessageReaders(); + List> messageReaders = adapter.getMessageReaders(); assertEquals(2, messageReaders.size()); assertHasMessageReader(messageReaders, String.class, TEXT_PLAIN); @@ -177,7 +177,7 @@ public class WebFluxConfigurationSupportTests { assertEquals(0, handler.getOrder()); - List> writers = handler.getMessageWriters(); + List> writers = handler.getMessageWriters(); assertEquals(8, writers.size()); assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM); @@ -203,7 +203,7 @@ public class WebFluxConfigurationSupportTests { assertEquals(100, handler.getOrder()); - List> writers = handler.getMessageWriters(); + List> writers = handler.getMessageWriters(); assertEquals(8, writers.size()); assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM); @@ -259,12 +259,12 @@ public class WebFluxConfigurationSupportTests { } - private void assertHasMessageReader(List> readers, Class clazz, MediaType mediaType) { + private void assertHasMessageReader(List> readers, Class clazz, MediaType mediaType) { ResolvableType type = ResolvableType.forClass(clazz); assertTrue(readers.stream().anyMatch(c -> mediaType == null || c.canRead(type, mediaType))); } - private void assertHasMessageWriter(List> writers, Class clazz, MediaType mediaType) { + private void assertHasMessageWriter(List> writers, Class clazz, MediaType mediaType) { ResolvableType type = ResolvableType.forClass(clazz); assertTrue(writers.stream().anyMatch(c -> mediaType == null || c.canWrite(type, mediaType))); } @@ -297,22 +297,22 @@ public class WebFluxConfigurationSupportTests { static class CustomMessageConverterConfig extends WebFluxConfigurationSupport { @Override - protected void configureMessageReaders(List> messageReaders) { + protected void configureMessageReaders(List> messageReaders) { messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder())); } @Override - protected void configureMessageWriters(List> messageWriters) { + protected void configureMessageWriters(List> messageWriters) { messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); } @Override - protected void extendMessageReaders(List> messageReaders) { + protected void extendMessageReaders(List> messageReaders) { messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); } @Override - protected void extendMessageWriters(List> messageWriters) { + protected void extendMessageWriters(List> messageWriters) { messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder())); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java index 61bbbe60b29..911436c0ec6 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java @@ -49,9 +49,10 @@ import org.springframework.web.reactive.function.server.support.ServerResponseRe import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import static org.junit.Assert.*; -import static org.springframework.web.reactive.function.BodyInserters.*; -import static org.springframework.web.reactive.function.server.RouterFunctions.*; +import static org.junit.Assert.assertEquals; +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.BodyInserters.fromPublisher; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; /** * Tests the use of {@link HandlerFunction} and {@link RouterFunction} in a @@ -123,12 +124,12 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr new HandlerStrategies() { @Override public Supplier>> messageReaders() { - return () -> getMessageReaders().stream(); + return () -> getMessageReaders().stream().map(reader -> (HttpMessageReader) reader); } @Override public Supplier>> messageWriters() { - return () -> getMessageWriters().stream(); + return () -> getMessageWriters().stream().map(writer -> (HttpMessageWriter) writer); } @Override diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java index 80c444708c3..98e7f9ba0c8 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolverTests.java @@ -39,7 +39,7 @@ import org.springframework.core.codec.StringDecoder; import org.springframework.http.HttpEntity; import org.springframework.http.RequestEntity; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; import org.springframework.util.ObjectUtils; import org.springframework.web.method.ResolvableMethod; @@ -73,7 +73,7 @@ public class HttpEntityArgumentResolverTests { private HttpEntityArgumentResolver createResolver() { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); readers.add(new DecoderHttpMessageReader<>(new StringDecoder())); return new HttpEntityArgumentResolver(readers, new ReactiveAdapterRegistry()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java index e4aa494766d..d2313967654 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java @@ -43,15 +43,15 @@ import org.springframework.core.ResolvableType; import org.springframework.core.codec.Decoder; import org.springframework.http.MediaType; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.method.HandlerMethod; -import org.springframework.web.reactive.BindingContext; import org.springframework.web.method.ResolvableMethod; +import org.springframework.web.reactive.BindingContext; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; @@ -305,7 +305,7 @@ public class MessageReaderArgumentResolverTests { @SuppressWarnings("Convert2MethodRef") private AbstractMessageReaderArgumentResolver resolver(Decoder... decoders) { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder))); return new AbstractMessageReaderArgumentResolver(readers) {}; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java index 039aba439f1..e863b62c8aa 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageWriterResultHandlerTests.java @@ -42,8 +42,8 @@ import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; @@ -71,8 +71,8 @@ public class MessageWriterResultHandlerTests { private final MockServerWebExchange exchange = MockServerHttpRequest.get("/path").toExchange(); - private AbstractMessageWriterResultHandler initResultHandler(HttpMessageWriter... writers) { - List> writerList; + private AbstractMessageWriterResultHandler initResultHandler(ServerHttpMessageWriter... writers) { + List> writerList; if (ObjectUtils.isEmpty(writers)) { writerList = new ArrayList<>(); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); @@ -141,7 +141,7 @@ public class MessageWriterResultHandlerTests { ByteArrayOutputStream body = new ByteArrayOutputStream(); MethodParameter type = on(TestController.class).resolveReturnType(OutputStream.class); - HttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); + ServerHttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); Mono mono = initResultHandler(writer).writeBody(body, type, this.exchange); StepVerifier.create(mono).expectError(IllegalStateException.class).verify(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java index 1b7debef3a0..fa65584af31 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolverTests.java @@ -35,7 +35,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.codec.StringDecoder; import org.springframework.http.codec.DecoderHttpMessageReader; -import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerHttpMessageReader; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.method.ResolvableMethod; @@ -66,7 +66,7 @@ public class RequestBodyArgumentResolverTests { @Before public void setup() { - List> readers = new ArrayList<>(); + List> readers = new ArrayList<>(); readers.add(new DecoderHttpMessageReader<>(new StringDecoder())); this.resolver = new RequestBodyArgumentResolver(readers, new ReactiveAdapterRegistry()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java index 1add66cb596..7b3f3b3320f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandlerTests.java @@ -29,8 +29,8 @@ import rx.Single; import org.springframework.core.codec.ByteBufferEncoder; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.stereotype.Controller; @@ -64,7 +64,7 @@ public class ResponseBodyResultHandlerTests { @Before public void setup() throws Exception { - List> writerList = new ArrayList<>(5); + List> writerList = new ArrayList<>(5); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())); writerList.add(new ResourceHttpMessageWriter()); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index 54b337d527f..048b09c6517 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -43,8 +43,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.EncoderHttpMessageWriter; -import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ResourceHttpMessageWriter; +import org.springframework.http.codec.ServerHttpMessageWriter; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerWebExchange; @@ -83,8 +83,8 @@ public class ResponseEntityResultHandlerTests { this.resultHandler = createHandler(); } - private ResponseEntityResultHandler createHandler(HttpMessageWriter... writers) { - List> writerList; + private ResponseEntityResultHandler createHandler(ServerHttpMessageWriter... writers) { + List> writerList; if (ObjectUtils.isEmpty(writers)) { writerList = new ArrayList<>(); writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder())); From 124cdb5c588376cea966e3c1ec4981a50239275c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 21 Mar 2017 10:57:21 -0400 Subject: [PATCH 5/5] Polish Javadoc of [Server]HttpMessage[Reader|Writer] --- .../http/codec/DecoderHttpMessageReader.java | 20 +++---- .../http/codec/EncoderHttpMessageWriter.java | 14 ++--- .../http/codec/FormHttpMessageReader.java | 10 ++-- .../http/codec/FormHttpMessageWriter.java | 12 ++-- .../http/codec/HttpMessageReader.java | 57 ++++++++++--------- .../http/codec/HttpMessageWriter.java | 49 ++++++++-------- .../http/codec/ResourceHttpMessageWriter.java | 2 +- .../http/codec/ServerHttpDecoder.java | 1 - .../http/codec/ServerHttpEncoder.java | 1 - .../http/codec/ServerHttpMessageReader.java | 47 +++++++-------- .../http/codec/ServerHttpMessageWriter.java | 30 +++++----- .../ServerSentEventHttpMessageReader.java | 8 +-- .../ServerSentEventHttpMessageWriter.java | 12 ++-- .../client/ExchangeStrategiesTests.java | 6 +- .../server/HandlerStrategiesTests.java | 6 +- 15 files changed, 135 insertions(+), 140 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index cd104d41cb5..bc3ee2c8001 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -81,19 +81,19 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { } @Override - public Flux read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, + public Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { - MediaType contentType = getContentType(inputMessage); - return this.decoder.decode(inputMessage.getBody(), elementType, contentType, hints); + MediaType contentType = getContentType(message); + return this.decoder.decode(message.getBody(), elementType, contentType, hints); } @Override - public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, + public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { - MediaType contentType = getContentType(inputMessage); - return this.decoder.decodeToMono(inputMessage.getBody(), elementType, contentType, hints); + MediaType contentType = getContentType(message); + return this.decoder.decodeToMono(message.getBody(), elementType, contentType, hints); } private MediaType getContentType(HttpMessage inputMessage) { @@ -105,22 +105,22 @@ public class DecoderHttpMessageReader implements ServerHttpMessageReader { // ServerHttpMessageReader... @Override - public Flux read(ResolvableType streamType, ResolvableType elementType, + public Flux read(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(4); - allHints.putAll(getReadHints(streamType, elementType, request, response)); + allHints.putAll(getReadHints(actualType, elementType, request, response)); allHints.putAll(hints); return read(elementType, request, allHints); } @Override - public Mono readMono(ResolvableType streamType, ResolvableType elementType, + public Mono readMono(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(4); - allHints.putAll(getReadHints(streamType, elementType, request, response)); + allHints.putAll(getReadHints(actualType, elementType, request, response)); allHints.putAll(hints); return readMono(elementType, request, allHints); diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 0a9dfa6166c..c16d5346996 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -120,10 +120,10 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { @Override public Mono write(Publisher inputStream, ResolvableType elementType, - MediaType mediaType, ReactiveHttpOutputMessage outputMessage, + MediaType mediaType, ReactiveHttpOutputMessage message, Map hints) { - HttpHeaders headers = outputMessage.getHeaders(); + HttpHeaders headers = message.getHeaders(); if (headers.getContentType() == null) { MediaType fallback = this.defaultMediaType; @@ -135,11 +135,11 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { } Flux body = this.encoder.encode(inputStream, - outputMessage.bufferFactory(), elementType, headers.getContentType(), hints); + message.bufferFactory(), elementType, headers.getContentType(), hints); return isStreamingMediaType(headers.getContentType()) ? - outputMessage.writeAndFlushWith(body.map(Flux::just)) : - outputMessage.writeWith(body); + message.writeAndFlushWith(body.map(Flux::just)) : + message.writeWith(body); } private static boolean useFallback(MediaType main, MediaType fallback) { @@ -162,12 +162,12 @@ public class EncoderHttpMessageWriter implements ServerHttpMessageWriter { // ServerHttpMessageWriter... @Override - public Mono write(Publisher inputStream, ResolvableType streamType, + public Mono write(Publisher inputStream, ResolvableType actualType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = new HashMap<>(); - allHints.putAll(getWriteHints(streamType, elementType, mediaType, request, response)); + allHints.putAll(getWriteHints(actualType, elementType, mediaType, request, response)); allHints.putAll(hints); return write(inputStream, elementType, mediaType, response, allHints); diff --git a/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageReader.java index edafcd0650b..c66690b4462 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageReader.java @@ -83,19 +83,19 @@ public class FormHttpMessageReader implements HttpMessageReader> read(ResolvableType elementType, - ReactiveHttpInputMessage inputMessage, Map hints) { + ReactiveHttpInputMessage message, Map hints) { - return Flux.from(readMono(elementType, inputMessage, hints)); + return Flux.from(readMono(elementType, message, hints)); } @Override public Mono> readMono(ResolvableType elementType, - ReactiveHttpInputMessage inputMessage, Map hints) { + ReactiveHttpInputMessage message, Map hints) { - MediaType contentType = inputMessage.getHeaders().getContentType(); + MediaType contentType = message.getHeaders().getContentType(); Charset charset = getMediaTypeCharset(contentType); - return inputMessage.getBody() + return message.getBody() .reduce(DataBuffer::write) .map(buffer -> { CharBuffer charBuffer = charset.decode(buffer.asByteBuffer()); diff --git a/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java index 0e04e85f94d..a5070a95177 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/FormHttpMessageWriter.java @@ -82,13 +82,13 @@ public class FormHttpMessageWriter implements HttpMessageWriter write(Publisher> inputStream, - ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage outputMessage, + ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage message, Map hints) { - MediaType contentType = outputMessage.getHeaders().getContentType(); + MediaType contentType = message.getHeaders().getContentType(); if (contentType == null) { contentType = MediaType.APPLICATION_FORM_URLENCODED; - outputMessage.getHeaders().setContentType(contentType); + message.getHeaders().setContentType(contentType); } Charset charset = getMediaTypeCharset(contentType); @@ -99,9 +99,9 @@ public class FormHttpMessageWriter implements HttpMessageWriter generateForm(form, charset)) .then(value -> { ByteBuffer byteBuffer = charset.encode(value); - DataBuffer buffer = outputMessage.bufferFactory().wrap(byteBuffer); - outputMessage.getHeaders().setContentLength(byteBuffer.remaining()); - return outputMessage.writeWith(Mono.just(buffer)); + DataBuffer buffer = message.bufferFactory().wrap(byteBuffer); + message.getHeaders().setContentLength(byteBuffer.remaining()); + return message.writeWith(Mono.just(buffer)); }); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java index a72d7ff03ea..009de9b0d8d 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -27,8 +27,10 @@ import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpInputMessage; /** - * Strategy interface that specifies a reader that can convert from the HTTP - * request body from a stream of bytes to Objects. + * Strategy for reading from a {@link ReactiveHttpInputMessage} and decoding + * the stream of bytes to Objects of type {@code }. + * + * @param the type of objects in the decoded output stream * * @author Rossen Stoyanchev * @author Arjen Poutsma @@ -38,40 +40,39 @@ import org.springframework.http.ReactiveHttpInputMessage; public interface HttpMessageReader { /** - * Indicates whether the given class can be read by this converter. - * @param elementType the stream element type to test for readability - * @param mediaType the media type to read, can be {@code null} if not specified. - * Typically the value of a {@code Content-Type} header. - * @return {@code true} if readable; {@code false} otherwise + * Return the {@link MediaType}'s that this reader supports. */ - boolean canRead(ResolvableType elementType, MediaType mediaType); + List getReadableMediaTypes(); /** - * Read a {@link Flux} of the given type form the given input message, and returns it. - * @param elementType the stream element type to return. This type must have previously been - * passed to the {@link #canRead canRead} method of this interface, which must have - * returned {@code true}. - * @param inputMessage the HTTP input message to read from - * @param hints additional information about how to read the body - * @return the converted {@link Flux} of elements + * Whether the given object type is supported by this reader. + * @param elementType the type of object to check + * @param mediaType the media type for the read, possibly {@code null} + * @return {@code true} if readable, {@code false} otherwise */ - Flux read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map hints); + boolean canRead(ResolvableType elementType, MediaType mediaType); /** - * Read a {@link Mono} of the given type form the given input message, and returns it. - * @param elementType the stream element type to return. This type must have previously been - * passed to the {@link #canRead canRead} method of this interface, which must have - * returned {@code true}. - * @param inputMessage the HTTP input message to read from - * @param hints additional information about how to read the body - * @return the converted {@link Mono} of object + * Read from the input message and encode to a stream of objects. + * + * @param elementType the type of objects in the stream which must have been + * previously checked via {@link #canRead(ResolvableType, MediaType)} + * @param message the message to read from + * @param hints additional information about how to read and decode the input + * @return the decoded stream of elements */ - Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map hints); + Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints); /** - * Return the list of {@link MediaType} objects that can be read by this converter. - * @return the list of supported readable media types + * Read from the input message and encode to a single object. + * + * @param elementType the type of objects in the stream which must have been + * previously checked via {@link #canRead(ResolvableType, MediaType)} + * @param message the message to read from + * @param hints additional information about how to read and decode the input + * @return the decoded object */ - List getReadableMediaTypes(); + Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints); + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java index 8a2f769affa..6083f905fa5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/HttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -27,8 +27,10 @@ import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; /** - * Strategy interface that specifies a converter that can convert a stream of - * Objects to a stream of bytes to be written to the HTTP response body. + * Strategy for encoding a stream of objects of type {@code } and writing + * the encoded stream of bytes to an {@link ReactiveHttpOutputMessage}. + * + * @param the type of objects in the input stream * * @author Rossen Stoyanchev * @author Arjen Poutsma @@ -38,34 +40,31 @@ import org.springframework.http.ReactiveHttpOutputMessage; public interface HttpMessageWriter { /** - * Indicates whether the given class can be written by this converter. - * @param elementType the stream element type to test for writability - * @param mediaType the media type to write, can be {@code null} if not specified. - * Typically the value of an {@code Accept} header. - * @return {@code true} if writable; {@code false} otherwise + * Return the {@link MediaType}'s that this writer supports. */ - boolean canWrite(ResolvableType elementType, MediaType mediaType); + List getWritableMediaTypes(); /** - * Write an given object to the given output message. - * @param inputStream the input stream to write - * @param elementType the stream element type to process. This type must have previously - * been passed to the {@link #canWrite} canWrite} method of this interface, which must - * have returned {@code true}. - * @param mediaType the content type to use when writing, typically the value of an - * {@code Accept} header. May be {@code null} to indicate that the default content - * type of the converter must be used. - * @param outputMessage the message to write to - * @param hints additional information about how to write - * @return a {@link Mono} that indicates completion or error + * Whether the given object type is supported by this writer. + * @param elementType the type of object to check + * @param mediaType the media type for the write, possibly {@code null} + * @return {@code true} if writable, {@code false} otherwise */ - Mono write(Publisher inputStream, ResolvableType elementType, - MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map hints); + boolean canWrite(ResolvableType elementType, MediaType mediaType); /** - * Return the list of {@link MediaType} objects that can be written by this converter. - * @return the list of supported readable media types + * Write an given stream of object to the output message. + * @param inputStream the objects to write + * @param elementType the type of objects in the stream which must have been + * previously checked via {@link #canWrite(ResolvableType, MediaType)} + * @param mediaType the content type for the write, possibly {@code null} to + * indicate that the default content type of the writer must be used. + * @param message the message to write to + * @param hints additional information about how to encode and write + * @return indicates completion or error */ - List getWritableMediaTypes(); + Mono write(Publisher inputStream, ResolvableType elementType, + MediaType mediaType, ReactiveHttpOutputMessage message, Map hints); + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java index 2f72b7210dc..3765d7ecc43 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java @@ -176,7 +176,7 @@ public class ResourceHttpMessageWriter implements ServerHttpMessageWriter write(Publisher inputStream, ResolvableType streamType, + public Mono write(Publisher inputStream, ResolvableType actualType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java index fc256c9bd93..f5b5dddf1ff 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpDecoder.java @@ -31,7 +31,6 @@ import org.springframework.http.server.reactive.ServerHttpResponse; */ public interface ServerHttpDecoder extends Decoder { - /** * Get decoding hints based on the server request or annotations on the * target controller method parameter. diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java index 20bc06583fc..7b5fa512b62 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpEncoder.java @@ -33,7 +33,6 @@ import org.springframework.http.server.reactive.ServerHttpResponse; */ public interface ServerHttpEncoder extends Encoder { - /** * Get decoding hints based on the server request or annotations on the * target controller method parameter. diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java index 5574f79f2d1..1509b42ddd3 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -27,46 +27,43 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; /** - * Server oriented {@link HttpMessageReader} that allows to resolve hints using annotations or - * perform additional operation using {@link ServerHttpRequest} or {@link ServerHttpResponse}. + * An extension of {@code HttpMessageReader} for decoding and reading the + * request body with extra information available on the server side. * * @author Sebastien Deleuze + * @author Rossen Stoyanchev * @since 5.0 */ public interface ServerHttpMessageReader extends HttpMessageReader { /** - * Read a {@link Flux} of the given type form the given input message with additional server related - * parameters which could be used to create some hints or set the response status for example. + * Decode and read the request body to an object stream. * - * Return hints that can be used to customize how the body should be read - * @param streamType the original type used in the method parameter. For annotation - * based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}. - * @param elementType the stream element type to return - * Typically the value of a {@code Content-Type} header. - * @param request the current HTTP request - * @param response the current HTTP response + * @param actualType the actual type of the target method parameter; for + * annotated controllers, the {@link MethodParameter} can be accessed via + * {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the output stream + * @param request the current request + * @param response the current response * @param hints additional information about how to read the body - * @return the converted {@link Flux} of elements + * @return the decoded stream of elements */ - Flux read(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request, + Flux read(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response, Map hints); /** - * Read a {@link Mono} of the given type form the given input message with additional server related - * parameters which could be used to create some hints or set the response status for example. + * Decode and read the request body to a single object. * - * Return hints that can be used to customize how the body should be read - * @param streamType the original type used in the method parameter. For annotation - * based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}. - * @param elementType the stream element type to return - * Typically the value of a {@code Content-Type} header. - * @param request the current HTTP request - * @param response the current HTTP response + * @param actualType the actual type of the target method parameter; for + * annotated controllers, the {@link MethodParameter} can be accessed via + * {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the output stream + * @param request the current request + * @param response the current response * @param hints additional information about how to read the body - * @return the converted {@link Mono} of object + * @return the decoded stream of elements */ - Mono readMono(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request, + Mono readMono(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response, Map hints); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java index 518e63b58ff..5758d728c5e 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerHttpMessageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -28,29 +28,29 @@ import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; /** - * Server oriented {@link HttpMessageWriter} that allows to resolve hints using annotations or - * perform additional operation using {@link ServerHttpRequest} or {@link ServerHttpResponse}. + * An extension of {@code HttpMessageWriter} for encoding and writing the + * response body with extra information available on the server side. * * @author Sebastien Deleuze + * @author Rossen Stoyanchev * @since 5.0 */ public interface ServerHttpMessageWriter extends HttpMessageWriter { /** - * Write a given object to the given output message with additional server related - * parameters which could be used to create some hints or set the response status for example. + * Encode and write the given object stream to the response. * - * @param streamType the original type used for the method return value. For annotation - * based controllers, the {@link MethodParameter} is available via {@link ResolvableType#getSource()}. - * Can be {@code null}. - * @param elementType the stream element type to process - * @param mediaType the content type to use when writing. May be {@code null} to - * indicate that the default content type of the converter must be used. - * @param request the current HTTP request - * @param response the current HTTP response - * @return a {@link Mono} that indicates completion or error + * @param actualType the actual return type of the method that returned the + * value; for annotated controllers, the {@link MethodParameter} can be + * accessed via {@link ResolvableType#getSource()}. + * @param elementType the type of Objects in the input stream + * @param mediaType the content type to use, possibly {@code null} indicating + * the default content type of the writer should be used. + * @param request the current request + * @param response the current response + * @return a {@link Mono} that indicates completion of writing or error */ - Mono write(Publisher inputStream, ResolvableType streamType, + Mono write(Publisher inputStream, ResolvableType actualType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints); diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java index ee9f4ffc9a6..bf2e182107c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java @@ -87,13 +87,13 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, + public Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { boolean hasSseWrapper = ServerSentEvent.class.isAssignableFrom(elementType.getRawClass()); ResolvableType dataType = (hasSseWrapper ? elementType.getGeneric(0) : elementType); - return Flux.from(inputMessage.getBody()) + return Flux.from(message.getBody()) .concatMap(ServerSentEventHttpMessageReader::splitOnNewline) .map(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); @@ -178,13 +178,13 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, + public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { // Let's give StringDecoder a chance since SSE is ordered ahead of it if (String.class.equals(elementType.getRawClass())) { - Flux body = inputMessage.getBody(); + Flux body = message.getBody(); return stringDecoder.decodeToMono(body, elementType, null, null).cast(Object.class); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java index 7f2032b137a..b25daae2f17 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageWriter.java @@ -87,14 +87,14 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter @Override public Mono write(Publisher inputStream, ResolvableType elementType, MediaType mediaType, - ReactiveHttpOutputMessage outputMessage, Map hints) { + ReactiveHttpOutputMessage message, Map hints) { - outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM); + message.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM); - DataBufferFactory bufferFactory = outputMessage.bufferFactory(); + DataBufferFactory bufferFactory = message.bufferFactory(); Flux> body = encode(inputStream, bufferFactory, elementType, hints); - return outputMessage.writeAndFlushWith(body); + return message.writeAndFlushWith(body); } private Flux> encode(Publisher inputStream, DataBufferFactory bufferFactory, @@ -164,14 +164,14 @@ public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter } @Override - public Mono write(Publisher inputStream, ResolvableType streamType, ResolvableType elementType, + public Mono write(Publisher inputStream, ResolvableType actualType, ResolvableType elementType, MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map hints) { Map allHints = this.dataEncoders.stream() .filter(encoder -> encoder instanceof ServerHttpEncoder) .map(encoder -> (ServerHttpEncoder) encoder) - .map(encoder -> encoder.getEncodeHints(streamType, elementType, mediaType, request, response)) + .map(encoder -> encoder.getEncodeHints(actualType, elementType, mediaType, request, response)) .reduce(new HashMap<>(), (t, u) -> { t.putAll(u); return t; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java index 314759c3f34..c54387fe82d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java @@ -97,7 +97,7 @@ public class ExchangeStrategiesTests { @Override public Mono write(Publisher inputStream, ResolvableType type, MediaType contentType, - ReactiveHttpOutputMessage outputMessage, + ReactiveHttpOutputMessage message, Map hints) { return Mono.empty(); } @@ -117,13 +117,13 @@ public class ExchangeStrategiesTests { } @Override - public Flux read(ResolvableType type, ReactiveHttpInputMessage inputMessage, + public Flux read(ResolvableType type, ReactiveHttpInputMessage message, Map hints) { return Flux.empty(); } @Override - public Mono readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage, + public Mono readMono(ResolvableType type, ReactiveHttpInputMessage message, Map hints) { return Mono.empty(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java index 0c94874eebf..8a32fc29116 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/HandlerStrategiesTests.java @@ -99,7 +99,7 @@ public class HandlerStrategiesTests { @Override public Mono write(Publisher inputStream, ResolvableType type, MediaType contentType, - ReactiveHttpOutputMessage outputMessage, + ReactiveHttpOutputMessage message, Map hints) { return Mono.empty(); } @@ -119,13 +119,13 @@ public class HandlerStrategiesTests { } @Override - public Flux read(ResolvableType type, ReactiveHttpInputMessage inputMessage, + public Flux read(ResolvableType type, ReactiveHttpInputMessage message, Map hints) { return Flux.empty(); } @Override - public Mono readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage, + public Mono readMono(ResolvableType type, ReactiveHttpInputMessage message, Map hints) { return Mono.empty(); }