diff --git a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java index d16a0a9090..518d3fac7e 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java @@ -17,9 +17,11 @@ package org.springframework.http.codec; import java.util.List; +import java.util.function.Consumer; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; +import org.springframework.lang.Nullable; /** * Defines a common interface for configuring either client or server HTTP @@ -213,6 +215,38 @@ public interface CodecConfigurer { * @param writer the writer to add */ void writer(HttpMessageWriter writer); + + /** + * Register a callback for the {@link DefaultCodecConfig configuration} + * applied to default codecs. This allows custom codecs to follow general + * guidelines applied to default ones, such as logging details and limiting + * the amount of buffered data. + * @param codecsConfigConsumer the default codecs configuration callback + * @since 5.1.12 + */ + void withDefaultCodecConfig(Consumer codecsConfigConsumer); + } + + + /** + * Common options applied to default codecs and passed in a callback to custom codecs + * so they get a chance to align their behavior on the default ones. + * @since 5.1.12 + */ + interface DefaultCodecConfig { + + /** + * Get the configured limit on the number of bytes that can be buffered whenever + * the input stream needs to be aggregated. + */ + @Nullable + Integer maxInMemorySize(); + + /** + * Whether to log form data at DEBUG level, and headers at TRACE level. + * Both may contain sensitive information. + */ + boolean isEnableLoggingRequestDetails(); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java index 505d19d0e2..a63e0be888 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java @@ -18,6 +18,7 @@ package org.springframework.http.codec.support; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.springframework.core.ResolvableType; import org.springframework.core.codec.Decoder; @@ -39,6 +40,8 @@ import org.springframework.util.Assert; */ abstract class BaseCodecConfigurer implements CodecConfigurer { + protected boolean customCodecsInitialized; + protected final BaseDefaultCodecs defaultCodecs; protected final DefaultCustomCodecs customCodecs; @@ -88,6 +91,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { @Override public List> getReaders() { + initializeCustomCodecs(); List> result = new ArrayList<>(); result.addAll(this.defaultCodecs.getTypedReaders()); @@ -113,6 +117,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { * same except for the multipart writer itself. */ protected List> getWritersInternal(boolean forMultipart) { + initializeCustomCodecs(); List> result = new ArrayList<>(); result.addAll(this.defaultCodecs.getTypedWriters(forMultipart)); @@ -128,6 +133,13 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { @Override public abstract CodecConfigurer clone(); + private void initializeCustomCodecs() { + if(!this.customCodecsInitialized) { + this.customCodecs.configConsumers.forEach(consumer -> consumer.accept(this.defaultCodecs)); + this.customCodecsInitialized = true; + } + } + /** * Default implementation of {@code CustomCodecs}. @@ -142,6 +154,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { private final List> objectWriters = new ArrayList<>(); + private final List> configConsumers = new ArrayList<>(); DefaultCustomCodecs() { } @@ -179,6 +192,10 @@ abstract class BaseCodecConfigurer implements CodecConfigurer { (canWriteObject ? this.objectWriters : this.typedWriters).add(writer); } + @Override + public void withDefaultCodecConfig(Consumer codecsConfigConsumer) { + this.configConsumers.add(codecsConfigConsumer); + } // Package private accessors... diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index 9be10da095..75ec9191dc 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -59,7 +59,7 @@ import org.springframework.util.ClassUtils; * @author Rossen Stoyanchev * @author Sebastien Deleuze */ -class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { +class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigurer.DefaultCodecConfig { static final boolean jackson2Present; @@ -158,8 +158,9 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { this.maxInMemorySize = byteCount; } + @Override @Nullable - protected Integer maxInMemorySize() { + public Integer maxInMemorySize() { return this.maxInMemorySize; } @@ -168,7 +169,8 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { this.enableLoggingRequestDetails = enable; } - protected boolean isEnableLoggingRequestDetails() { + @Override + public boolean isEnableLoggingRequestDetails() { return this.enableLoggingRequestDetails; } diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index eb4a3a0d45..02f41655f4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -18,6 +18,7 @@ package org.springframework.http.codec.support; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -336,6 +337,18 @@ public class CodecConfigurerTests { assertFalse(encoders.containsAll(Arrays.asList(jacksonEncoder, jaxb2Encoder, protoEncoder))); } + @Test + public void withDefaultCodecConfig() { + AtomicBoolean callbackCalled = new AtomicBoolean(false); + this.configurer.defaultCodecs().enableLoggingRequestDetails(true); + this.configurer.customCodecs().withDefaultCodecConfig(config -> { + assertTrue(config.isEnableLoggingRequestDetails()); + callbackCalled.compareAndSet(false, true); + }); + this.configurer.getReaders(); + assertTrue(callbackCalled.get()); + } + private Decoder getNextDecoder(List> readers) { HttpMessageReader reader = readers.get(this.index.getAndIncrement()); assertEquals(DecoderHttpMessageReader.class, reader.getClass()); diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index 172a48b8cd..a43d12b030 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -883,16 +883,44 @@ The following example shows how to do so for client-side requests: [subs="verbatim,quotes"] ---- Consumer consumer = configurer -> - configurer.defaultCodecs().enableLoggingRequestDetails(true); + configurer.defaultCodecs().enableLoggingRequestDetails(true); WebClient webClient = WebClient.builder() - .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build()) - .build(); + .exchangeStrategies(strategies -> strategies.codecs(consumer)) + .build(); ---- ==== +[[webflux-codecs-custom]] +==== Custom codecs +Applications can register custom codecs for supporting additional media types, +or specific behaviors that are not supported by the default codecs. +Some configuration options expressed by developers are enforced on default codecs. +Custom codecs might want to get a chance to align with those preferences, +like <> +or <>. + +The following example shows how to do so for client-side requests: + +==== +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + Consumer consumer = configurer -> { + CustomDecoder customDecoder = new CustomDecoder(); + configurer.customCodecs().decoder(customDecoder); + configurer.customCodecs().withDefaultCodecConfig(config -> + customDecoder.maxInMemorySize(config.maxInMemorySize()) + ); + } + + WebClient webClient = WebClient.builder() + .exchangeStrategies(strategies -> strategies.codecs(consumer)) + .build(); +---- +==== [[webflux-dispatcher-handler]] == `DispatcherHandler`