String encoding for any MIME type

CharSequenceEncoder now supports all MIME types, however since encoding
Flux<String> can overlap with other encoders (e.g. SSE) there are now
two ways to create a CharSequenceEncoder -- with support for text/plain
only or with support for any MIME type.

In WebFlux configuration we insert one CharSequenceEncoder for
text/plain (as we have so far) and a second instance with support for
any MIME type at the very end.

Issue: SPR-15374
This commit is contained in:
Rossen Stoyanchev
2017-03-22 17:54:18 -04:00
parent 2896c5d2ab
commit 3d68c496f1
16 changed files with 106 additions and 39 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.DataBufferDecoder;
import org.springframework.core.codec.DataBufferEncoder;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.ResourceDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.convert.converter.Converter;
@@ -469,6 +470,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
*/
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
}
/**
* Adds default converters that sub-classes can call from
* {@link #configureMessageWriters(List)}.
@@ -477,15 +479,24 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
writers.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
writers.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writers.add(new EncoderHttpMessageWriter<>(new DataBufferEncoder()));
writers.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writers.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
writers.add(new ResourceHttpMessageWriter());
if (jaxb2Present) {
writers.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
}
if (jackson2Present) {
Jackson2JsonEncoder jacksonEncoder = new Jackson2JsonEncoder();
writers.add(new EncoderHttpMessageWriter<>(jacksonEncoder));
writers.add(new ServerSentEventHttpMessageWriter(jacksonEncoder));
writers.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
writers.add(new ServerSentEventHttpMessageWriter(getSseEncoder()));
writers.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
}
private Encoder<?> getSseEncoder() {
if (jackson2Present) {
return new Jackson2JsonEncoder();
}
else {
return null;
}
}

View File

@@ -75,12 +75,10 @@ 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()));
if (jackson2Present) {
// SSE ahead of String e.g. "test/event-stream" + Flux<String>
messageReader(new ServerSentEventHttpMessageReader(new Jackson2JsonDecoder()));
}
messageReader(new DecoderHttpMessageReader<>(new StringDecoder(false)));
if (jaxb2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
@@ -90,10 +88,19 @@ class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder {
}
}
private Decoder<?> getSseDecoder() {
if (jackson2Present) {
return new Jackson2JsonDecoder();
}
else {
return null;
}
}
private void defaultWriters() {
messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
messageWriter(new ResourceHttpMessageWriter());
messageWriter(new FormHttpMessageWriter());
if (jaxb2Present) {

View File

@@ -31,6 +31,7 @@ import org.springframework.core.codec.ByteArrayEncoder;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.Encoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
@@ -88,7 +89,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
messageWriter(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
messageWriter(new ResourceHttpMessageWriter());
if (jaxb2Present) {
@@ -97,13 +98,24 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
}
if (jackson2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
messageWriter(new EncoderHttpMessageWriter<>(jsonEncoder));
messageWriter(new ServerSentEventHttpMessageWriter(jsonEncoder));
messageWriter(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
messageWriter(new ServerSentEventHttpMessageWriter(getSseEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
localeResolver(DEFAULT_LOCALE_RESOLVER);
}
private Encoder<?> getSseEncoder() {
if (jackson2Present) {
return new Jackson2JsonEncoder();
}
else {
return null;
}
}
public void applicationContext(ApplicationContext applicationContext) {
applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader);
applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter);

View File

@@ -186,8 +186,8 @@ public class DispatcherHandlerErrorTests {
@Bean
public ResponseBodyResultHandler resultHandler() {
return new ResponseBodyResultHandler(
Collections.singletonList(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())),
return new ResponseBodyResultHandler(Collections.singletonList(
new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly())),
new HeaderContentTypeResolver());
}

View File

@@ -178,7 +178,7 @@ public class WebFluxConfigurationSupportTests {
assertEquals(0, handler.getOrder());
List<ServerHttpMessageWriter<?>> writers = handler.getMessageWriters();
assertEquals(8, writers.size());
assertEquals(9, writers.size());
assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
@@ -204,7 +204,7 @@ public class WebFluxConfigurationSupportTests {
assertEquals(100, handler.getOrder());
List<ServerHttpMessageWriter<?>> writers = handler.getMessageWriters();
assertEquals(8, writers.size());
assertEquals(9, writers.size());
assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
@@ -303,7 +303,7 @@ public class WebFluxConfigurationSupportTests {
@Override
protected void configureMessageWriters(List<ServerHttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
}
@Override

View File

@@ -77,13 +77,14 @@ public class BodyInsertersTests {
public void createContext() {
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
messageWriters.add(new ResourceHttpMessageWriter());
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
messageWriters.add(new EncoderHttpMessageWriter<>(jsonEncoder));
messageWriters.add(new ServerSentEventHttpMessageWriter(jsonEncoder));
messageWriters.add(new FormHttpMessageWriter());
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
this.context = new BodyInserter.Context() {
@Override

View File

@@ -104,7 +104,7 @@ public class DefaultClientRequestBuilderTests {
.body(inserter).build();
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);

View File

@@ -191,7 +191,9 @@ public class DefaultEntityResponseBuilderTests {
MockServerWebExchange exchange = MockServerHttpRequest.get("http://localhost").toExchange();
HandlerStrategies strategies = HandlerStrategies.empty().messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())).build();
HandlerStrategies strategies = HandlerStrategies.empty()
.messageWriter(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()))
.build();
StepVerifier.create(result)
.consumeNextWith(response -> {

View File

@@ -76,7 +76,7 @@ public class MessageWriterResultHandlerTests {
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));

View File

@@ -66,7 +66,7 @@ public class ResponseBodyResultHandlerTests {
public void setup() throws Exception {
List<ServerHttpMessageWriter<?>> writerList = new ArrayList<>(5);
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));

View File

@@ -88,10 +88,11 @@ public class ResponseEntityResultHandlerTests {
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.allMimeTypes()));
}
else {
writerList = Arrays.asList(writers);

View File

@@ -104,7 +104,7 @@ public class HttpMessageWriterViewTests {
@Test
public void extractObjectMultipleMatchesNotSupported() throws Exception {
HttpMessageWriterView view = new HttpMessageWriterView(new CharSequenceEncoder());
HttpMessageWriterView view = new HttpMessageWriterView(CharSequenceEncoder.allMimeTypes());
view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2")));
this.model.addAttribute("foo1", "bar1");
this.model.addAttribute("foo2", "bar2");