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 <rstoyanchev@pivotal.io>
This commit is contained in:
Brian Clozel 2019-12-02 22:52:55 +01:00
parent d1ab81587c
commit decbb9ccf9
4 changed files with 100 additions and 5 deletions

View File

@ -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<DefaultCodecConfig> 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();
}
}

View File

@ -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<HttpMessageReader<?>> getReaders() {
initializeCustomCodecs();
List<HttpMessageReader<?>> 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<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
initializeCustomCodecs();
List<HttpMessageWriter<?>> 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<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
private final List<Consumer<DefaultCodecConfig>> 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<DefaultCodecConfig> codecsConfigConsumer) {
this.configConsumers.add(codecsConfigConsumer);
}
// Package private accessors...
List<HttpMessageReader<?>> getTypedReaders() {

View File

@ -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;
}

View File

@ -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 <<webflux-codecs-limits, enforcing buffering limits>>
or <<webflux-logging-sensitive-data, logging sensitive data>>.
The following example shows how to do so for client-side requests:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
Consumer<ClientCodecConfigurer> 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`