From decbb9ccf9dd4da87f2f3aeca9b05ce24824beb9 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 2 Dec 2019 22:52:55 +0100 Subject: [PATCH] Provide default codecs config callback to custom codecs As a follow-up of gh-23961, this change provides a way for custom codecs to align with the default codecs' behavior on common features like buffer size limits and logging request details. Closes gh-24118 Co-authored-by: Rossen Stoyanchev --- .../http/codec/CodecConfigurer.java | 34 ++++++++++++++ .../codec/support/BaseCodecConfigurer.java | 18 ++++++++ .../http/codec/support/BaseDefaultCodecs.java | 8 ++-- src/docs/asciidoc/web/webflux.adoc | 45 ++++++++++++++++++- 4 files changed, 100 insertions(+), 5 deletions(-) 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 2e69bfc14b..4caf849b1a 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 @@ -212,6 +214,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 31b3784a3f..325c407e5e 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.customCodecs.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.customCodecs.getTypedWriters()); @@ -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,11 @@ 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... List> getTypedReaders() { 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 ec38c10a29..c3a7fa3859 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 @@ -60,7 +60,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; @@ -159,8 +159,9 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { this.maxInMemorySize = byteCount; } + @Override @Nullable - protected Integer maxInMemorySize() { + public Integer maxInMemorySize() { return this.maxInMemorySize; } @@ -169,7 +170,8 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs { this.enableLoggingRequestDetails = enable; } - protected boolean isEnableLoggingRequestDetails() { + @Override + public boolean isEnableLoggingRequestDetails() { return this.enableLoggingRequestDetails; } diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index cff1be3b71..4c501f1d71 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -950,7 +950,7 @@ The following example shows how to do so for client-side requests: configurer.defaultCodecs().enableLoggingRequestDetails(true); WebClient webClient = WebClient.builder() - .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build()) + .exchangeStrategies(strategies -> strategies.codecs(consumer)) .build(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -959,12 +959,53 @@ The following example shows how to do so for client-side requests: val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) } val webClient = WebClient.builder() - .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).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",role="primary"] +.Java +---- + 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(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> + val customDecoder = CustomDecoder() + configurer.customCodecs().decoder(customDecoder) + configurer.customCodecs().withDefaultCodecConfig({ config -> + customDecoder.maxInMemorySize(config.maxInMemorySize()) + }) + } + + val webClient = WebClient.builder() + .exchangeStrategies({ strategies -> strategies.codecs(consumer) }) + .build() +---- [[webflux-dispatcher-handler]] == `DispatcherHandler`