String decoding for text only vs any MIME type

Follow-up to:
3d68c496f1

StringDecoder can be created in text-only vs "*/*" mode which in turn
allows a more intuitive order of client side decoders, e.g. SSE does
not have to be ahead of StringDecoder.

The commit also explicitly disables String from the supported types in
Jackson2Decoder leaving it to the StringDecoder in "*/*" mode which
comes after. This does not change the current arrangement since the
the StringDecoder ahead having "*/*" picks up JSON content just the
same.

From a broader perspective this change allows any decoder to deal with
String if it wants to after examining the content type be it the SSE
or another, custom decoder. For Jackson there is very little value in
decoding to String which works only if the output contains a single
JSON string but will fail to parse anything else (JSON object/array)
while StringDecoder in "*/*" mode will not fail.

Issue: SPR-15374
This commit is contained in:
Rossen Stoyanchev
2017-03-23 16:23:34 -04:00
parent a287e67992
commit 0662dbf044
17 changed files with 75 additions and 49 deletions

View File

@@ -331,7 +331,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
readers.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
readers.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
readers.add(new DecoderHttpMessageReader<>(new DataBufferDecoder()));
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
readers.add(new DecoderHttpMessageReader<>(new ResourceDecoder()));
if (jaxb2Present) {
readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));

View File

@@ -75,17 +75,17 @@ class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder {
}
private void defaultReaders() {
// SSE first (constrained to "text/event-stream")
messageReader(new ServerSentEventHttpMessageReader(getSseDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReader(new DecoderHttpMessageReader<>(new StringDecoder(false)));
messageReader(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(false)));
if (jaxb2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
}
if (jackson2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
}
messageReader(new ServerSentEventHttpMessageReader(getSseDecoder()));
messageReader(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(false)));
}
private Decoder<?> getSseDecoder() {

View File

@@ -84,7 +84,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
public void defaultConfiguration() {
messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReader(new DecoderHttpMessageReader<>(new StringDecoder()));
messageReader(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
messageReader(new FormHttpMessageReader());
messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));

View File

@@ -114,10 +114,11 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
public RequestMappingHandlerAdapter() {
// TODO: improve with better (shared) defaults
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(new DataBufferDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
}

View File

@@ -298,7 +298,7 @@ public class WebFluxConfigurationSupportTests {
@Override
protected void configureMessageReaders(List<ServerHttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly(true)));
}
@Override

View File

@@ -67,7 +67,7 @@ public class BodyExtractorsTests {
public void createContext() {
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
messageReaders.add(new FormHttpMessageReader());

View File

@@ -123,7 +123,7 @@ public class DefaultClientResponseTests {
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.body(toMono(String.class));
@@ -144,7 +144,7 @@ public class DefaultClientResponseTests {
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
@@ -159,7 +159,7 @@ public class DefaultClientResponseTests {
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
@@ -183,7 +183,7 @@ public class DefaultClientResponseTests {
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);
@@ -199,7 +199,7 @@ public class DefaultClientResponseTests {
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);

View File

@@ -52,9 +52,10 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.WebSession;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.web.reactive.function.BodyExtractors.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
/**
* @author Arjen Poutsma
@@ -190,7 +191,7 @@ public class DefaultServerRequestTests {
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultRequest.body(toMono(String.class));
@@ -210,7 +211,7 @@ public class DefaultServerRequestTests {
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultRequest.bodyToMono(String.class);
@@ -230,7 +231,7 @@ public class DefaultServerRequestTests {
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
.singleton(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultRequest.bodyToFlux(String.class);

View File

@@ -74,7 +74,7 @@ public class HttpEntityArgumentResolverTests {
private HttpEntityArgumentResolver createResolver() {
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
return new HttpEntityArgumentResolver(readers, new ReactiveAdapterRegistry());
}

View File

@@ -67,7 +67,7 @@ public class RequestBodyArgumentResolverTests {
@Before
public void setup() {
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
this.resolver = new RequestBodyArgumentResolver(readers, new ReactiveAdapterRegistry());
}