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:
parent
d1ab81587c
commit
decbb9ccf9
|
@ -17,9 +17,11 @@
|
||||||
package org.springframework.http.codec;
|
package org.springframework.http.codec;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.springframework.core.codec.Decoder;
|
import org.springframework.core.codec.Decoder;
|
||||||
import org.springframework.core.codec.Encoder;
|
import org.springframework.core.codec.Encoder;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a common interface for configuring either client or server HTTP
|
* Defines a common interface for configuring either client or server HTTP
|
||||||
|
@ -212,6 +214,38 @@ public interface CodecConfigurer {
|
||||||
* @param writer the writer to add
|
* @param writer the writer to add
|
||||||
*/
|
*/
|
||||||
void writer(HttpMessageWriter<?> writer);
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.http.codec.support;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.codec.Decoder;
|
import org.springframework.core.codec.Decoder;
|
||||||
|
@ -39,6 +40,8 @@ import org.springframework.util.Assert;
|
||||||
*/
|
*/
|
||||||
abstract class BaseCodecConfigurer implements CodecConfigurer {
|
abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
|
|
||||||
|
protected boolean customCodecsInitialized;
|
||||||
|
|
||||||
protected final BaseDefaultCodecs defaultCodecs;
|
protected final BaseDefaultCodecs defaultCodecs;
|
||||||
|
|
||||||
protected final DefaultCustomCodecs customCodecs;
|
protected final DefaultCustomCodecs customCodecs;
|
||||||
|
@ -88,6 +91,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<HttpMessageReader<?>> getReaders() {
|
public List<HttpMessageReader<?>> getReaders() {
|
||||||
|
initializeCustomCodecs();
|
||||||
List<HttpMessageReader<?>> result = new ArrayList<>();
|
List<HttpMessageReader<?>> result = new ArrayList<>();
|
||||||
|
|
||||||
result.addAll(this.customCodecs.getTypedReaders());
|
result.addAll(this.customCodecs.getTypedReaders());
|
||||||
|
@ -113,6 +117,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
* same except for the multipart writer itself.
|
* same except for the multipart writer itself.
|
||||||
*/
|
*/
|
||||||
protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
|
protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
|
||||||
|
initializeCustomCodecs();
|
||||||
List<HttpMessageWriter<?>> result = new ArrayList<>();
|
List<HttpMessageWriter<?>> result = new ArrayList<>();
|
||||||
|
|
||||||
result.addAll(this.customCodecs.getTypedWriters());
|
result.addAll(this.customCodecs.getTypedWriters());
|
||||||
|
@ -128,6 +133,13 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
@Override
|
@Override
|
||||||
public abstract CodecConfigurer clone();
|
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}.
|
* Default implementation of {@code CustomCodecs}.
|
||||||
|
@ -142,6 +154,7 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
|
|
||||||
private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
|
private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
|
||||||
|
|
||||||
|
private final List<Consumer<DefaultCodecConfig>> configConsumers = new ArrayList<>();
|
||||||
|
|
||||||
DefaultCustomCodecs() {
|
DefaultCustomCodecs() {
|
||||||
}
|
}
|
||||||
|
@ -179,6 +192,11 @@ abstract class BaseCodecConfigurer implements CodecConfigurer {
|
||||||
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
|
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void withDefaultCodecConfig(Consumer<DefaultCodecConfig> codecsConfigConsumer) {
|
||||||
|
this.configConsumers.add(codecsConfigConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
// Package private accessors...
|
// Package private accessors...
|
||||||
|
|
||||||
List<HttpMessageReader<?>> getTypedReaders() {
|
List<HttpMessageReader<?>> getTypedReaders() {
|
||||||
|
|
|
@ -60,7 +60,7 @@ import org.springframework.util.ClassUtils;
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Sebastien Deleuze
|
* @author Sebastien Deleuze
|
||||||
*/
|
*/
|
||||||
class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigurer.DefaultCodecConfig {
|
||||||
|
|
||||||
static final boolean jackson2Present;
|
static final boolean jackson2Present;
|
||||||
|
|
||||||
|
@ -159,8 +159,9 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
||||||
this.maxInMemorySize = byteCount;
|
this.maxInMemorySize = byteCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
protected Integer maxInMemorySize() {
|
public Integer maxInMemorySize() {
|
||||||
return this.maxInMemorySize;
|
return this.maxInMemorySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +170,8 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
||||||
this.enableLoggingRequestDetails = enable;
|
this.enableLoggingRequestDetails = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isEnableLoggingRequestDetails() {
|
@Override
|
||||||
|
public boolean isEnableLoggingRequestDetails() {
|
||||||
return this.enableLoggingRequestDetails;
|
return this.enableLoggingRequestDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -950,7 +950,7 @@ The following example shows how to do so for client-side requests:
|
||||||
configurer.defaultCodecs().enableLoggingRequestDetails(true);
|
configurer.defaultCodecs().enableLoggingRequestDetails(true);
|
||||||
|
|
||||||
WebClient webClient = WebClient.builder()
|
WebClient webClient = WebClient.builder()
|
||||||
.exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build())
|
.exchangeStrategies(strategies -> strategies.codecs(consumer))
|
||||||
.build();
|
.build();
|
||||||
----
|
----
|
||||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
[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 consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }
|
||||||
|
|
||||||
val webClient = WebClient.builder()
|
val webClient = WebClient.builder()
|
||||||
.exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build())
|
.exchangeStrategies({ strategies -> strategies.codecs(consumer) })
|
||||||
.build()
|
.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]]
|
[[webflux-dispatcher-handler]]
|
||||||
== `DispatcherHandler`
|
== `DispatcherHandler`
|
||||||
|
|
Loading…
Reference in New Issue