Merge refactoring steps in HTTP codec package

This commit is contained in:
Rossen Stoyanchev
2017-03-21 11:15:21 -04:00
43 changed files with 548 additions and 651 deletions

View File

@@ -31,13 +31,6 @@ import org.springframework.util.MimeType;
*/
public abstract class AbstractEncoder<T> implements Encoder<T> {
/**
* 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<MimeType> encodableMimeTypes;

View File

@@ -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<T> implements ServerHttpMessageReader<T> {
private HttpMessageReader<T> reader;
public AbstractServerHttpMessageReader(HttpMessageReader<T> reader) {
this.reader = reader;
}
@Override
public boolean canRead(ResolvableType elementType, MediaType mediaType) {
return this.reader.canRead(elementType, mediaType);
}
@Override
public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
return this.reader.read(elementType, inputMessage, hints);
}
@Override
public Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
return this.reader.readMono(elementType, inputMessage, hints);
}
@Override
public List<MediaType> getReadableMediaTypes() {
return this.reader.getReadableMediaTypes();
}
@Override
public Flux<T> read(ResolvableType streamType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> mergedHints = new HashMap<>(hints);
mergedHints.putAll(resolveReadHints(streamType, elementType, request));
return (this.reader instanceof ServerHttpMessageReader ?
((ServerHttpMessageReader<T>)this.reader).read(streamType, elementType, request, response, mergedHints) :
this.read(elementType, request, mergedHints));
}
@Override
public Mono<T> readMono(ResolvableType streamType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> mergedHints = new HashMap<>(hints);
mergedHints.putAll(resolveReadHints(streamType, elementType, request));
return (this.reader instanceof ServerHttpMessageReader ?
((ServerHttpMessageReader<T>)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<String, Object> resolveReadHints(ResolvableType streamType,
ResolvableType elementType, ServerHttpRequest request);
}

View File

@@ -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<T> implements ServerHttpMessageWriter<T> {
private HttpMessageWriter<T> writer;
public AbstractServerHttpMessageWriter(HttpMessageWriter<T> writer) {
this.writer = writer;
}
@Override
public boolean canWrite(ResolvableType elementType, MediaType mediaType) {
return this.writer.canWrite(elementType, mediaType);
}
@Override
public List<MediaType> getWritableMediaTypes() {
return this.writer.getWritableMediaTypes();
}
@Override
public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType,
MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
return this.writer.write(inputStream, elementType, mediaType, outputMessage, hints);
}
@Override
public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType streamType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> mergedHints = new HashMap<>(hints);
mergedHints.putAll(resolveWriteHints(streamType, elementType, mediaType, request));
return (this.writer instanceof ServerHttpMessageWriter ?
((ServerHttpMessageWriter<T>)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<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request);
}

View File

@@ -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,17 +29,23 @@ 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;
/**
* Implementation of {@code HttpMessageReader} delegating to a {@link Decoder}.
* {@code HttpMessageReader} that wraps and delegates to a {@link Decoder}.
*
* <p>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
* @author Rossen Stoyanchev
* @since 5.0
*/
public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
public class DecoderHttpMessageReader<T> implements ServerHttpMessageReader<T> {
private final Decoder<T> decoder;
@@ -74,19 +81,19 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
}
@Override
public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage message,
Map<String, Object> 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<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
public Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage message,
Map<String, Object> 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) {
@@ -94,4 +101,44 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
}
// ServerHttpMessageReader...
@Override
public Flux<T> read(ResolvableType actualType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> allHints = new HashMap<>(4);
allHints.putAll(getReadHints(actualType, elementType, request, response));
allHints.putAll(hints);
return read(elementType, request, allHints);
}
@Override
public Mono<T> readMono(ResolvableType actualType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> allHints = new HashMap<>(4);
allHints.putAll(getReadHints(actualType, elementType, request, response));
allHints.putAll(hints);
return readMono(elementType, request, allHints);
}
/**
* 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<String, Object> 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();
}
}

View File

@@ -16,6 +16,9 @@
package org.springframework.http.codec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,20 +32,31 @@ 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;
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}.
*
* <p>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
* @author Rossen Stoyanchev
* @since 5.0
*/
public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
public class EncoderHttpMessageWriter<T> implements ServerHttpMessageWriter<T> {
/**
* 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<MediaType> DEFAULT_STREAMING_MEDIA_TYPES =
Collections.singletonList(MediaType.APPLICATION_STREAM_JSON);
private final Encoder<T> encoder;
@@ -50,6 +64,8 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
private final MediaType defaultMediaType;
private final List<MediaType> streamingMediaTypes = new ArrayList<>(1);
/**
* Create an instance wrapping the given {@link Encoder}.
@@ -59,6 +75,7 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
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<MediaType> mediaTypes) {
@@ -78,6 +95,23 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
return this.mediaTypes;
}
/**
* Configure "streaming" media types for which flushing should be performed
* automatically vs at the end of the input stream.
* <p>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<MediaType> mediaTypes) {
this.streamingMediaTypes.addAll(mediaTypes);
}
/**
* Return the configured list of "streaming" media types.
*/
public List<MediaType> getStreamingMediaTypes() {
return Collections.unmodifiableList(this.streamingMediaTypes);
}
@Override
public boolean canWrite(ResolvableType elementType, MediaType mediaType) {
@@ -86,10 +120,10 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
@Override
public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType,
MediaType mediaType, ReactiveHttpOutputMessage outputMessage,
MediaType mediaType, ReactiveHttpOutputMessage message,
Map<String, Object> hints) {
HttpHeaders headers = outputMessage.getHeaders();
HttpHeaders headers = message.getHeaders();
if (headers.getContentType() == null) {
MediaType fallback = this.defaultMediaType;
@@ -101,10 +135,11 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
}
Flux<DataBuffer> body = this.encoder.encode(inputStream,
outputMessage.bufferFactory(), elementType, headers.getContentType(), hints);
message.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()) ?
message.writeAndFlushWith(body.map(Flux::just)) :
message.writeWith(body);
}
private static boolean useFallback(MediaType main, MediaType fallback) {
@@ -119,4 +154,38 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
return main;
}
private boolean isStreamingMediaType(MediaType contentType) {
return this.streamingMediaTypes.stream().anyMatch(contentType::isCompatibleWith);
}
// ServerHttpMessageWriter...
@Override
public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType actualType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request,
ServerHttpResponse response, Map<String, Object> hints) {
Map<String, Object> allHints = new HashMap<>();
allHints.putAll(getWriteHints(actualType, elementType, mediaType, request, response));
allHints.putAll(hints);
return write(inputStream, elementType, mediaType, response, allHints);
}
/**
* 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<String, Object> 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();
}
}

View File

@@ -83,19 +83,19 @@ public class FormHttpMessageReader implements HttpMessageReader<MultiValueMap<St
@Override
public Flux<MultiValueMap<String, String>> read(ResolvableType elementType,
ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
ReactiveHttpInputMessage message, Map<String, Object> hints) {
return Flux.from(readMono(elementType, inputMessage, hints));
return Flux.from(readMono(elementType, message, hints));
}
@Override
public Mono<MultiValueMap<String, String>> readMono(ResolvableType elementType,
ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
ReactiveHttpInputMessage message, Map<String, Object> 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());

View File

@@ -82,13 +82,13 @@ public class FormHttpMessageWriter implements HttpMessageWriter<MultiValueMap<St
@Override
public Mono<Void> write(Publisher<? extends MultiValueMap<String, String>> inputStream,
ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage outputMessage,
ResolvableType elementType, MediaType mediaType, ReactiveHttpOutputMessage message,
Map<String, Object> 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<MultiValueMap<St
.map(form -> 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));
});
}

View File

@@ -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 <T>}.
*
* @param <T> 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<T> {
/**
* 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.
*/
List<MediaType> getReadableMediaTypes();
/**
* 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
*/
boolean canRead(ResolvableType elementType, MediaType mediaType);
/**
* 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
* 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
*/
Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints);
Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints);
/**
* 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 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
*/
Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage, Map<String, Object> hints);
Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints);
/**
* Return the list of {@link MediaType} objects that can be read by this converter.
* @return the list of supported readable media types
*/
List<MediaType> getReadableMediaTypes();
}

View File

@@ -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 <T>} and writing
* the encoded stream of bytes to an {@link ReactiveHttpOutputMessage}.
*
* @param <T> 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<T> {
/**
* 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.
*/
List<MediaType> getWritableMediaTypes();
/**
* 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
*/
boolean canWrite(ResolvableType elementType, MediaType mediaType);
/**
* 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
* 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
*/
Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType,
MediaType mediaType, ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints);
MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints);
/**
* Return the list of {@link MediaType} objects that can be written by this converter.
* @return the list of supported readable media types
*/
List<MediaType> getWritableMediaTypes();
}

View File

@@ -1,65 +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.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints:
* <ul>
* <li>{@code @JsonView} + {@code @RequestBody} annotated handler method parameter</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 5.0
* @see com.fasterxml.jackson.annotation.JsonView
*/
public class Jackson2ServerHttpMessageReader extends AbstractServerHttpMessageReader<Object> {
public Jackson2ServerHttpMessageReader(HttpMessageReader<Object> reader) {
super(reader);
}
@Override
protected Map<String, Object> resolveReadHints(ResolvableType streamType,
ResolvableType elementType, ServerHttpRequest request) {
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();
}
}

View File

@@ -1,103 +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 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;
/**
* 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 AbstractServerHttpMessageWriter<Object> {
public Jackson2ServerHttpMessageWriter(Encoder<Object> encoder) {
super(new EncoderHttpMessageWriter<>(encoder));
}
public Jackson2ServerHttpMessageWriter(HttpMessageWriter<Object> writer) {
super(writer);
}
@Override
protected Map<String, Object> resolveWriteHints(ResolvableType streamType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
Map<String, Object> 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<Void> write(Publisher<?> inputStream, ResolvableType elementType, MediaType mediaType,
ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
if ((mediaType != null) && mediaType.isCompatibleWith(MediaType.APPLICATION_STREAM_JSON)) {
Map<String, Object> 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<Void> write(Publisher<?> inputStream, ResolvableType streamType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
if ((mediaType != null) && mediaType.isCompatibleWith(MediaType.APPLICATION_STREAM_JSON)) {
Map<String, Object> 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);
}
}

View File

@@ -176,7 +176,7 @@ public class ResourceHttpMessageWriter implements ServerHttpMessageWriter<Resour
@Override
@SuppressWarnings("unchecked")
public Mono<Void> write(Publisher<? extends Resource> inputStream, ResolvableType streamType,
public Mono<Void> write(Publisher<? extends Resource> inputStream, ResolvableType actualType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request,
ServerHttpResponse response, Map<String, Object> hints) {

View File

@@ -0,0 +1,50 @@
/*
* 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<T> extends Decoder<T> {
/**
* 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<String, Object> getDecodeHints(ResolvableType actualType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response);
}

View File

@@ -0,0 +1,52 @@
/*
* 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<T> extends Encoder<T> {
/**
* 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<String, Object> getEncodeHints(ResolvableType actualType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response);
}

View File

@@ -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<T> extends HttpMessageReader<T> {
/**
* 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<T> read(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request,
Flux<T> read(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request,
ServerHttpResponse response, Map<String, Object> 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<T> readMono(ResolvableType streamType, ResolvableType elementType, ServerHttpRequest request,
Mono<T> readMono(ResolvableType actualType, ResolvableType elementType, ServerHttpRequest request,
ServerHttpResponse response, Map<String, Object> hints);
}

View File

@@ -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<T> extends HttpMessageWriter<T> {
/**
* 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<Void> write(Publisher<? extends T> inputStream, ResolvableType streamType,
Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType actualType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request,
ServerHttpResponse response, Map<String, Object> hints);

View File

@@ -87,13 +87,13 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader<Objec
@Override
public Flux<Object> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
public Flux<Object> read(ResolvableType elementType, ReactiveHttpInputMessage message,
Map<String, Object> 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<Objec
}
@Override
public Mono<Object> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
public Mono<Object> readMono(ResolvableType elementType, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
// Let's give StringDecoder a chance since SSE is ordered ahead of it
if (String.class.equals(elementType.getRawClass())) {
Flux<DataBuffer> body = inputMessage.getBody();
Flux<DataBuffer> body = message.getBody();
return stringDecoder.decodeToMono(body, elementType, null, null).cast(Object.class);
}

View File

@@ -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<Object> {
public class ServerSentEventHttpMessageWriter implements ServerHttpMessageWriter<Object> {
/**
* Server-Sent Events hint key expecting a {@link Boolean} value which when set to true
@@ -85,14 +87,14 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType elementType, MediaType mediaType,
ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
ReactiveHttpOutputMessage message, Map<String, Object> hints) {
outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
message.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
DataBufferFactory bufferFactory = outputMessage.bufferFactory();
DataBufferFactory bufferFactory = message.bufferFactory();
Flux<Publisher<DataBuffer>> body = encode(inputStream, bufferFactory, elementType, hints);
return outputMessage.writeAndFlushWith(body);
return message.writeAndFlushWith(body);
}
private Flux<Publisher<DataBuffer>> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory,
@@ -161,4 +163,23 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
return Mono.just(buffer);
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType actualType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response,
Map<String, Object> hints) {
Map<String, Object> allHints = this.dataEncoders.stream()
.filter(encoder -> encoder instanceof ServerHttpEncoder)
.map(encoder -> (ServerHttpEncoder<?>) encoder)
.map(encoder -> encoder.getEncodeHints(actualType, elementType, mediaType, request, response))
.reduce(new HashMap<>(), (t, u) -> {
t.putAll(u);
return t;
});
allHints.putAll(hints);
return write(inputStream, elementType, mediaType, response, allHints);
}
}

View File

@@ -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<Object> {
public class Jackson2JsonDecoder extends AbstractJackson2Codec implements ServerHttpDecoder<Object> {
private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true);
@@ -123,4 +127,27 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Decode
});
}
// ServerHttpDecoder...
@Override
public Map<String, Object> 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();
}
}

View File

@@ -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<Object> {
public class Jackson2JsonEncoder extends AbstractJackson2Codec implements ServerHttpEncoder<Object> {
private final PrettyPrinter ssePrettyPrinter;
@@ -144,4 +151,28 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Encode
return buffer;
}
// ServerHttpEncoder...
@Override
public Map<String, Object> getEncodeHints(ResolvableType actualType, ResolvableType elementType,
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) {
Map<String, Object> 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;
}
}

View File

@@ -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<HttpMessageReader<?>> messageReaders) {
protected void configureMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
this.configurers.configureMessageReaders(messageReaders);
}
@Override
protected void extendMessageReaders(List<HttpMessageReader<?>> messageReaders) {
protected void extendMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
this.configurers.extendMessageReaders(messageReaders);
}
@@ -101,12 +101,12 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport
}
@Override
protected void configureMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
this.configurers.configureMessageWriters(messageWriters);
}
@Override
protected void extendMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
protected void extendMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
this.configurers.extendMessageWriters(messageWriters);
}

View File

@@ -48,11 +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.Jackson2ServerHttpMessageReader;
import org.springframework.http.codec.Jackson2ServerHttpMessageWriter;
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;
@@ -106,9 +104,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
private PathMatchConfigurer pathMatchConfigurer;
private List<HttpMessageReader<?>> messageReaders;
private List<ServerHttpMessageReader<?>> messageReaders;
private List<HttpMessageWriter<?>> messageWriters;
private List<ServerHttpMessageWriter<?>> messageWriters;
private ApplicationContext applicationContext;
@@ -301,7 +299,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
* <p>Use {@link #configureMessageReaders} to configure the list or
* {@link #extendMessageReaders} to add in addition to the default ones.
*/
protected final List<HttpMessageReader<?>> getMessageReaders() {
protected final List<ServerHttpMessageReader<?>> getMessageReaders() {
if (this.messageReaders == null) {
this.messageReaders = new ArrayList<>();
configureMessageReaders(this.messageReaders);
@@ -320,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<HttpMessageReader<?>> messageReaders) {
protected void configureMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
}
/**
@@ -329,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<HttpMessageReader<?>> readers) {
protected final void addDefaultHttpMessageReaders(List<ServerHttpMessageReader<?>> readers) {
readers.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
readers.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
readers.add(new DecoderHttpMessageReader<>(new DataBufferDecoder()));
@@ -339,8 +337,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()));
}
}
@@ -348,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<HttpMessageReader<?>> messageReaders) {
protected void extendMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
}
/**
@@ -453,7 +450,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
* <p>Use {@link #configureMessageWriters(List)} to configure the list or
* {@link #extendMessageWriters(List)} to add in addition to the default ones.
*/
protected final List<HttpMessageWriter<?>> getMessageWriters() {
protected final List<ServerHttpMessageWriter<?>> getMessageWriters() {
if (this.messageWriters == null) {
this.messageWriters = new ArrayList<>();
configureMessageWriters(this.messageWriters);
@@ -471,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<HttpMessageWriter<?>> messageWriters) {
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
}
/**
* Adds default converters that sub-classes can call from
* {@link #configureMessageWriters(List)}.
*/
protected final void addDefaultHttpMessageWriters(List<HttpMessageWriter<?>> writers) {
protected final void addDefaultHttpMessageWriters(List<ServerHttpMessageWriter<?>> writers) {
List<Encoder<?>> sseDataEncoders = new ArrayList<>();
writers.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
writers.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
@@ -489,21 +486,17 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
}
if (jackson2Present) {
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
writers.add(new Jackson2ServerHttpMessageWriter(encoder));
writers.add(new EncoderHttpMessageWriter<>(encoder));
sseDataEncoders.add(encoder);
HttpMessageWriter<Object> writer = new ServerSentEventHttpMessageWriter(sseDataEncoders);
writers.add(new Jackson2ServerHttpMessageWriter(writer));
}
else {
writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders));
}
writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders));
}
/**
* 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<HttpMessageWriter<?>> messageWriters) {
protected void extendMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
}
@Bean

View File

@@ -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<HttpMessageReader<?>> readers) {
default void configureMessageReaders(List<ServerHttpMessageReader<?>> 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<HttpMessageReader<?>> readers) {
default void extendMessageReaders(List<ServerHttpMessageReader<?>> 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<HttpMessageWriter<?>> writers) {
default void configureMessageWriters(List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> writers) {
default void extendMessageWriters(List<ServerHttpMessageWriter<?>> writers) {
}
/**

View File

@@ -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<HttpMessageReader<?>> readers) {
public void configureMessageReaders(List<ServerHttpMessageReader<?>> readers) {
this.delegates.stream().forEach(delegate -> delegate.configureMessageReaders(readers));
}
@Override
public void extendMessageReaders(List<HttpMessageReader<?>> readers) {
public void extendMessageReaders(List<ServerHttpMessageReader<?>> readers) {
this.delegates.stream().forEach(delegate -> delegate.extendMessageReaders(readers));
}
@@ -101,12 +101,12 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer {
}
@Override
public void configureMessageWriters(List<HttpMessageWriter<?>> writers) {
public void configureMessageWriters(List<ServerHttpMessageWriter<?>> writers) {
this.delegates.stream().forEach(delegate -> delegate.configureMessageWriters(writers));
}
@Override
public void extendMessageWriters(List<HttpMessageWriter<?>> writers) {
public void extendMessageWriters(List<ServerHttpMessageWriter<?>> writers) {
this.delegates.stream().forEach(delegate -> delegate.extendMessageWriters(writers));
}

View File

@@ -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)));
}

View File

@@ -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}.
*
* <p>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<HttpMessageReader<?>> messageReaders;
private final List<ServerHttpMessageReader<?>> messageReaders;
private final List<MediaType> 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<HttpMessageReader<?>> readers) {
protected AbstractMessageReaderArgumentResolver(List<ServerHttpMessageReader<?>> 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<HttpMessageReader<?>> messageReaders,
protected AbstractMessageReaderArgumentResolver(List<ServerHttpMessageReader<?>> messageReaders,
ReactiveAdapterRegistry adapterRegistry) {
super(adapterRegistry);
@@ -95,7 +94,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
/**
* Return the configured message converters.
*/
public List<HttpMessageReader<?>> getMessageReaders() {
public List<ServerHttpMessageReader<?>> 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<String, Object> 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)));

View File

@@ -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<HttpMessageWriter<?>> messageWriters;
private final List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> messageWriters,
protected AbstractMessageWriterResultHandler(List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> messageWriters,
protected AbstractMessageWriterResultHandler(List<ServerHttpMessageWriter<?>> messageWriters,
RequestedContentTypeResolver contentTypeResolver,
ReactiveAdapterRegistry adapterRegistry) {
@@ -85,7 +84,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa
/**
* Return the configured message converters.
*/
public List<HttpMessageWriter<?>> getMessageWriters() {
public List<ServerHttpMessageWriter<?>> 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());
}
}
}

View File

@@ -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<HttpMessageReader<?>> readers, ReactiveAdapterRegistry registry) {
public HttpEntityArgumentResolver(List<ServerHttpMessageReader<?>> readers,
ReactiveAdapterRegistry registry) {
super(readers, registry);
}

View File

@@ -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<HttpMessageReader<?>> readers, ReactiveAdapterRegistry registry) {
public RequestBodyArgumentResolver(List<ServerHttpMessageReader<?>> readers,
ReactiveAdapterRegistry registry) {
super(readers, registry);
}

View File

@@ -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<HttpMessageReader<?>> messageReaders = new ArrayList<>(10);
private final List<ServerHttpMessageReader<?>> 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<HttpMessageReader<?>> messageReaders) {
public void setMessageReaders(List<ServerHttpMessageReader<?>> 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<HttpMessageReader<?>> getMessageReaders() {
public List<ServerHttpMessageReader<?>> getMessageReaders() {
return this.messageReaders;
}

View File

@@ -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}.
*
* <p>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<HttpMessageWriter<?>> writers,
public ResponseBodyResultHandler(List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> writers,
public ResponseBodyResultHandler(List<ServerHttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(writers, resolver, registry);

View File

@@ -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<HttpMessageWriter<?>> writers,
public ResponseEntityResultHandler(List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> writers,
public ResponseEntityResultHandler(List<ServerHttpMessageWriter<?>> writers,
RequestedContentTypeResolver resolver, ReactiveAdapterRegistry registry) {
super(writers, resolver, registry);

View File

@@ -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<List<HttpMessageReader<?>>> readers;
private ArgumentCaptor<List<ServerHttpMessageReader<?>>> readers;
@Captor
private ArgumentCaptor<List<HttpMessageWriter<?>>> writers;
private ArgumentCaptor<List<ServerHttpMessageWriter<?>>> writers;
@Captor
private ArgumentCaptor<FormatterRegistry> formatterRegistry;

View File

@@ -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<HttpMessageReader<?>> readers = adapter.getMessageReaders();
List<ServerHttpMessageReader<?>> 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<HttpMessageReader<?>> messageReaders = adapter.getMessageReaders();
List<ServerHttpMessageReader<?>> 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<HttpMessageWriter<?>> writers = handler.getMessageWriters();
List<ServerHttpMessageWriter<?>> 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<HttpMessageWriter<?>> writers = handler.getMessageWriters();
List<ServerHttpMessageWriter<?>> 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<HttpMessageReader<?>> readers, Class<?> clazz, MediaType mediaType) {
private void assertHasMessageReader(List<ServerHttpMessageReader<?>> 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<HttpMessageWriter<?>> writers, Class<?> clazz, MediaType mediaType) {
private void assertHasMessageWriter(List<ServerHttpMessageWriter<?>> 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<HttpMessageReader<?>> messageReaders) {
protected void configureMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
}
@Override
protected void configureMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
}
@Override
protected void extendMessageReaders(List<HttpMessageReader<?>> messageReaders) {
protected void extendMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
}
@Override
protected void extendMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
protected void extendMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
}
}

View File

@@ -97,7 +97,7 @@ public class ExchangeStrategiesTests {
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage outputMessage,
ReactiveHttpOutputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}
@@ -117,13 +117,13 @@ public class ExchangeStrategiesTests {
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage inputMessage,
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage,
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}

View File

@@ -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<Stream<HttpMessageReader<?>>> messageReaders() {
return () -> getMessageReaders().stream();
return () -> getMessageReaders().stream().map(reader -> (HttpMessageReader<?>) reader);
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return () -> getMessageWriters().stream();
return () -> getMessageWriters().stream().map(writer -> (HttpMessageWriter<?>) writer);
}
@Override

View File

@@ -99,7 +99,7 @@ public class HandlerStrategiesTests {
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage outputMessage,
ReactiveHttpOutputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}
@@ -119,13 +119,13 @@ public class HandlerStrategiesTests {
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage inputMessage,
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage,
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage message,
Map<String, Object> hints) {
return Mono.empty();
}

View File

@@ -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<HttpMessageReader<?>> readers = new ArrayList<>();
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
return new HttpEntityArgumentResolver(readers, new ReactiveAdapterRegistry());
}

View File

@@ -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<HttpMessageReader<?>> readers = new ArrayList<>();
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder)));
return new AbstractMessageReaderArgumentResolver(readers) {};
}

View File

@@ -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<HttpMessageWriter<?>> writerList;
private AbstractMessageWriterResultHandler initResultHandler(ServerHttpMessageWriter<?>... writers) {
List<ServerHttpMessageWriter<?>> 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<Void> mono = initResultHandler(writer).writeBody(body, type, this.exchange);
StepVerifier.create(mono).expectError(IllegalStateException.class).verify();

View File

@@ -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<HttpMessageReader<?>> readers = new ArrayList<>();
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
this.resolver = new RequestBodyArgumentResolver(readers, new ReactiveAdapterRegistry());
}

View File

@@ -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<HttpMessageWriter<?>> writerList = new ArrayList<>(5);
List<ServerHttpMessageWriter<?>> writerList = new ArrayList<>(5);
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new ResourceHttpMessageWriter());

View File

@@ -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<HttpMessageWriter<?>> writerList;
private ResponseEntityResultHandler createHandler(ServerHttpMessageWriter<?>... writers) {
List<ServerHttpMessageWriter<?>> writerList;
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));