[Encoder|Decoder]HttpMessageWriter server support
There is a natural way to implement ServerHttpMessage[Reader|Writer] from [Encoder|Decoder]HttpMessageWriter by resolving hints first via a protected method and then delegating to the regular read or write. There is no downside either since it does not prevent [Encoder|Decoder]HttpMessageWriter from being used for both client and server scenarios while they're more useful. As a positive side effect AbstractServerHttpMessage[Reader|Writer] can be removed further simplfications can be made (in a future commit) to accept ServerHttpMessageWriter for configuration purposes on the server side and remove instanceof checks for ServerHttpMessageWriter.
This commit is contained in:
parent
1be2d8343d
commit
5f8bc4552f
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.http.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -28,6 +29,8 @@ import org.springframework.core.codec.Decoder;
|
|||
import org.springframework.http.HttpMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
|
@ -38,7 +41,7 @@ import org.springframework.util.Assert;
|
|||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
|
||||
public class DecoderHttpMessageReader<T> implements ServerHttpMessageReader<T> {
|
||||
|
||||
private final Decoder<T> decoder;
|
||||
|
||||
|
|
@ -94,4 +97,39 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
|
|||
return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
|
||||
// ServerHttpMessageReader...
|
||||
|
||||
@Override
|
||||
public Flux<T> read(ResolvableType streamType, ResolvableType elementType,
|
||||
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
|
||||
|
||||
Map<String, Object> allHints = new HashMap<>(4);
|
||||
allHints.putAll(resolveReadHints(streamType, elementType, request, response));
|
||||
allHints.putAll(hints);
|
||||
|
||||
return read(elementType, request, allHints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> readMono(ResolvableType streamType, ResolvableType elementType,
|
||||
ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> hints) {
|
||||
|
||||
Map<String, Object> allHints = new HashMap<>(4);
|
||||
allHints.putAll(resolveReadHints(streamType, elementType, request, response));
|
||||
allHints.putAll(hints);
|
||||
|
||||
return readMono(elementType, request, allHints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve hints to pass to the decoder, e.g. by checking for annotations
|
||||
* on a controller method parameter or checking the server request.
|
||||
*/
|
||||
protected Map<String, Object> resolveReadHints(ResolvableType streamType,
|
||||
ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.http.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -29,6 +31,8 @@ import org.springframework.core.io.buffer.DataBuffer;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.core.codec.AbstractEncoder.FLUSHING_STRATEGY_HINT;
|
||||
|
|
@ -42,7 +46,7 @@ import static org.springframework.core.codec.AbstractEncoder.FlushingStrategy.AF
|
|||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
||||
public class EncoderHttpMessageWriter<T> implements ServerHttpMessageWriter<T> {
|
||||
|
||||
private final Encoder<T> encoder;
|
||||
|
||||
|
|
@ -119,4 +123,29 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
|||
return main;
|
||||
}
|
||||
|
||||
|
||||
// ServerHttpMessageWriter...
|
||||
|
||||
@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> allHints = new HashMap<>();
|
||||
allHints.putAll(resolveWriteHints(streamType, elementType, mediaType, request, response));
|
||||
allHints.putAll(hints);
|
||||
|
||||
return write(inputStream, elementType, mediaType, response, allHints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve hints to pass to the encoder, e.g. by checking for annotations
|
||||
* on a controller method parameter or checking the server request.
|
||||
*/
|
||||
protected Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
|
||||
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -23,8 +23,10 @@ import com.fasterxml.jackson.annotation.JsonView;
|
|||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.http.codec.json.AbstractJackson2Codec;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints:
|
||||
|
|
@ -33,18 +35,21 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
|||
* </ul>
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
* @see com.fasterxml.jackson.annotation.JsonView
|
||||
*/
|
||||
public class Jackson2ServerHttpMessageReader extends AbstractServerHttpMessageReader<Object> {
|
||||
public class Jackson2ServerHttpMessageReader extends DecoderHttpMessageReader<Object> {
|
||||
|
||||
public Jackson2ServerHttpMessageReader(HttpMessageReader<Object> reader) {
|
||||
super(reader);
|
||||
|
||||
public Jackson2ServerHttpMessageReader(Decoder<Object> decoder) {
|
||||
super(decoder);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> resolveReadHints(ResolvableType streamType,
|
||||
ResolvableType elementType, ServerHttpRequest request) {
|
||||
ResolvableType elementType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
Object source = streamType.getSource();
|
||||
MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null);
|
||||
|
|
|
|||
|
|
@ -44,38 +44,13 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
|
|||
* @since 5.0
|
||||
* @see com.fasterxml.jackson.annotation.JsonView
|
||||
*/
|
||||
public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWriter<Object> {
|
||||
public class Jackson2ServerHttpMessageWriter extends EncoderHttpMessageWriter<Object> {
|
||||
|
||||
|
||||
public Jackson2ServerHttpMessageWriter(Encoder<Object> encoder) {
|
||||
super(new EncoderHttpMessageWriter<>(encoder));
|
||||
super(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,
|
||||
|
|
@ -100,4 +75,26 @@ public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWr
|
|||
}
|
||||
return super.write(inputStream, streamType, elementType, mediaType, request, response, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
|
||||
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -491,12 +491,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
|
|||
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
|
||||
writers.add(new Jackson2ServerHttpMessageWriter(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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue