Introduce ServerHttpMessageWriter/Reader to resolve hints
Issue: SPR-14693
This commit is contained in:
parent
084daa7fb5
commit
e74c59bf30
|
@ -18,6 +18,7 @@ package org.springframework.web.reactive.result.method.annotation;
|
|||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -32,6 +33,7 @@ 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.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
@ -115,8 +117,8 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyRequired,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
Class<?> bodyType = ResolvableType.forMethodParameter(bodyParameter).resolve();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapterTo(bodyType);
|
||||
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapterTo(bodyType.resolve());
|
||||
|
||||
ResolvableType elementType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
if (adapter != null) {
|
||||
|
@ -130,9 +132,15 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
}
|
||||
|
||||
for (HttpMessageReader<?> reader : getMessageReaders()) {
|
||||
if (reader.canRead(elementType, mediaType, Collections.emptyMap())) {
|
||||
|
||||
Map<String, Object> hints = (reader instanceof ServerHttpMessageReader ?
|
||||
((ServerHttpMessageReader<?>)reader).resolveReadHints(bodyType, elementType,
|
||||
mediaType, exchange.getRequest()) : Collections.emptyMap());
|
||||
|
||||
if (reader.canRead(elementType, mediaType, hints)) {
|
||||
|
||||
if (adapter != null && adapter.getDescriptor().isMultiValue()) {
|
||||
Flux<?> flux = reader.read(elementType, request, Collections.emptyMap())
|
||||
Flux<?> flux = reader.read(elementType, request, hints)
|
||||
.onErrorResumeWith(ex -> Flux.error(getReadError(ex, bodyParameter)));
|
||||
if (checkRequired(adapter, isBodyRequired)) {
|
||||
flux = flux.switchIfEmpty(Flux.error(getRequiredBodyError(bodyParameter)));
|
||||
|
@ -143,7 +151,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
|||
return Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = reader.readMono(elementType, request, Collections.emptyMap())
|
||||
Mono<?> mono = reader.readMono(elementType, request, hints)
|
||||
.otherwise(ex -> Mono.error(getReadError(ex, bodyParameter)));
|
||||
if (checkRequired(adapter, isBodyRequired)) {
|
||||
mono = mono.otherwiseIfEmpty(Mono.error(getRequiredBodyError(bodyParameter)));
|
||||
|
|
|
@ -17,6 +17,7 @@ package org.springframework.web.reactive.result.method.annotation;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
@ -28,6 +29,7 @@ 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.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
@ -89,10 +91,10 @@ public abstract class AbstractMessageWriterResultHandler extends ContentNegotiat
|
|||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Mono<Void> writeBody(Object body, MethodParameter bodyType, ServerWebExchange exchange) {
|
||||
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
|
||||
|
||||
Class<?> bodyClass = bodyType.getParameterType();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(bodyClass, body);
|
||||
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(bodyType.resolve(), body);
|
||||
|
||||
Publisher<?> publisher;
|
||||
ResolvableType elementType;
|
||||
|
@ -100,11 +102,11 @@ public abstract class AbstractMessageWriterResultHandler extends ContentNegotiat
|
|||
publisher = adapter.toPublisher(body);
|
||||
elementType = adapter.getDescriptor().isNoValue() ?
|
||||
ResolvableType.forClass(Void.class) :
|
||||
ResolvableType.forMethodParameter(bodyType).getGeneric(0);
|
||||
bodyType.getGeneric(0);
|
||||
}
|
||||
else {
|
||||
publisher = Mono.justOrEmpty(body);
|
||||
elementType = ResolvableType.forMethodParameter(bodyType);
|
||||
elementType = bodyType;
|
||||
}
|
||||
|
||||
if (void.class == elementType.getRawClass() || Void.class == elementType.getRawClass()) {
|
||||
|
@ -121,10 +123,14 @@ public abstract class AbstractMessageWriterResultHandler extends ContentNegotiat
|
|||
|
||||
if (bestMediaType != null) {
|
||||
for (HttpMessageWriter<?> messageWriter : getMessageWriters()) {
|
||||
if (messageWriter.canWrite(elementType, bestMediaType, Collections.emptyMap())) {
|
||||
Map<String, Object> hints = (messageWriter instanceof ServerHttpMessageWriter ?
|
||||
((ServerHttpMessageWriter<?>)messageWriter).resolveWriteHints(bodyType, elementType,
|
||||
bestMediaType, exchange.getRequest()) : Collections.emptyMap());
|
||||
if (messageWriter.canWrite(elementType, bestMediaType, hints)) {
|
||||
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
return messageWriter.write((Publisher) publisher, elementType,
|
||||
bestMediaType, response, Collections.emptyMap());
|
||||
bestMediaType, response, hints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@link HttpMessageReader} wrapper to extend that implements {@link ServerHttpMessageReader} in order
|
||||
* to allow providing hints.
|
||||
*
|
||||
* @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, Map<String, Object> hints) {
|
||||
return this.reader.canRead(elementType, mediaType, hints);
|
||||
}
|
||||
|
||||
@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 final Map<String, Object> resolveReadHints(ResolvableType streamType,
|
||||
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
|
||||
|
||||
Map<String, Object> hints = new HashMap<>();
|
||||
if (this.reader instanceof ServerHttpMessageReader) {
|
||||
hints.putAll(((ServerHttpMessageReader<T>)this.reader).resolveReadHints(streamType, elementType, mediaType, request));
|
||||
}
|
||||
hints.putAll(resolveReadHintsInternal(streamType, elementType, mediaType, request));
|
||||
return hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that returns hints which can be used to customize how the body should be read.
|
||||
* Invoked from {@link #resolveReadHints}.
|
||||
* @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
|
||||
* @param mediaType the media type to read, can be {@code null} if not specified.
|
||||
* Typically the value of a {@code Content-Type} header.
|
||||
* @param request the current HTTP request
|
||||
* @return Additional information about how to read the body
|
||||
*/
|
||||
protected abstract Map<String, Object> resolveReadHintsInternal(ResolvableType streamType,
|
||||
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request);
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@link HttpMessageWriter} wrapper to extend that implements {@link ServerHttpMessageWriter} in order
|
||||
* to allow providing hints.
|
||||
*
|
||||
* @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, Map<String, Object> hints) {
|
||||
return this.writer.canWrite(elementType, mediaType, hints);
|
||||
}
|
||||
|
||||
@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 List<MediaType> getWritableMediaTypes() {
|
||||
return this.writer.getWritableMediaTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, Object> resolveWriteHints(ResolvableType streamType,
|
||||
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
|
||||
|
||||
Map<String, Object> hints = new HashMap<>();
|
||||
if (this.writer instanceof ServerHttpMessageWriter) {
|
||||
hints.putAll(((ServerHttpMessageWriter<T>)this.writer).resolveWriteHints(streamType, elementType, mediaType, request));
|
||||
}
|
||||
hints.putAll(resolveWriteHintsInternal(streamType, elementType, mediaType, request));
|
||||
return hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that returns hints which can be used to customize how the body should be written.
|
||||
* Invoked from {@link #resolveWriteHints}.
|
||||
* @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> resolveWriteHintsInternal(ResolvableType streamType,
|
||||
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request);
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* Server and annotation based controller specific {@link HttpMessageReader} that allows to
|
||||
* resolve hints using annotations or request based information.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ServerHttpMessageReader<T> extends HttpMessageReader<T> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param mediaType the media type to read, can be {@code null} if not specified.
|
||||
* Typically the value of a {@code Content-Type} header.
|
||||
* @param request the current HTTP request
|
||||
* @return Additional information about how to read the body
|
||||
*/
|
||||
Map<String, Object> resolveReadHints(ResolvableType streamType, ResolvableType elementType,
|
||||
MediaType mediaType, ServerHttpRequest request);
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* Server oriented {@link HttpMessageWriter} that allows to resolve hints using annotations or
|
||||
* request based information.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ServerHttpMessageWriter<T> extends HttpMessageWriter<T> {
|
||||
|
||||
/**
|
||||
* Return hints that can be used to customize how the body should be written
|
||||
* @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
|
||||
*/
|
||||
Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
|
||||
MediaType mediaType, ServerHttpRequest request);
|
||||
|
||||
}
|
Loading…
Reference in New Issue