Skip to content

Commit

Permalink
Provide default codecs config callback to custom codecs
Browse files Browse the repository at this point in the history
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-24119
Co-authored-by: Rossen Stoyanchev <rstoyanchev@pivotal.io>
  • Loading branch information
bclozel committed Dec 2, 2019
1 parent 83683a1 commit a21df0c
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 6 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -213,6 +215,38 @@ interface CustomCodecs {
* @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();
}

}
Expand Up @@ -18,6 +18,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
Expand All @@ -39,6 +40,8 @@
*/
abstract class BaseCodecConfigurer implements CodecConfigurer {

protected boolean customCodecsInitialized;

protected final BaseDefaultCodecs defaultCodecs;

protected final DefaultCustomCodecs customCodecs;
Expand Down Expand Up @@ -88,6 +91,7 @@ public CustomCodecs customCodecs() {

@Override
public List<HttpMessageReader<?>> getReaders() {
initializeCustomCodecs();
List<HttpMessageReader<?>> result = new ArrayList<>();

result.addAll(this.defaultCodecs.getTypedReaders());
Expand All @@ -113,6 +117,7 @@ public List<HttpMessageWriter<?>> getWriters() {
* same except for the multipart writer itself.
*/
protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
initializeCustomCodecs();
List<HttpMessageWriter<?>> result = new ArrayList<>();

result.addAll(this.defaultCodecs.getTypedWriters(forMultipart));
Expand All @@ -128,6 +133,13 @@ protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
@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}.
Expand All @@ -142,6 +154,7 @@ protected static final class DefaultCustomCodecs implements CustomCodecs {

private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();

private final List<Consumer<DefaultCodecConfig>> configConsumers = new ArrayList<>();

DefaultCustomCodecs() {
}
Expand Down Expand Up @@ -179,6 +192,10 @@ public void writer(HttpMessageWriter<?> writer) {
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
}

@Override
public void withDefaultCodecConfig(Consumer<DefaultCodecConfig> codecsConfigConsumer) {
this.configConsumers.add(codecsConfigConsumer);
}

// Package private accessors...

Expand Down
Expand Up @@ -59,7 +59,7 @@
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigurer.DefaultCodecConfig {

static final boolean jackson2Present;

Expand Down Expand Up @@ -158,8 +158,9 @@ public void maxInMemorySize(int byteCount) {
this.maxInMemorySize = byteCount;
}

@Override
@Nullable
protected Integer maxInMemorySize() {
public Integer maxInMemorySize() {
return this.maxInMemorySize;
}

Expand All @@ -168,7 +169,8 @@ public void enableLoggingRequestDetails(boolean enable) {
this.enableLoggingRequestDetails = enable;
}

protected boolean isEnableLoggingRequestDetails() {
@Override
public boolean isEnableLoggingRequestDetails() {
return this.enableLoggingRequestDetails;
}

Expand Down
Expand Up @@ -18,6 +18,7 @@

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;

Expand Down Expand Up @@ -336,6 +337,18 @@ public void cloneDefaultCodecs() {
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<HttpMessageReader<?>> readers) {
HttpMessageReader<?> reader = readers.get(this.index.getAndIncrement());
assertEquals(DecoderHttpMessageReader.class, reader.getClass());
Expand Down
34 changes: 31 additions & 3 deletions src/docs/asciidoc/web/webflux.adoc
Expand Up @@ -883,16 +883,44 @@ The following example shows how to do so for client-side requests:
[subs="verbatim,quotes"]
----
Consumer<ClientCodecConfigurer> 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 <<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"]
----
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();
----
====

[[webflux-dispatcher-handler]]
== `DispatcherHandler`
Expand Down

0 comments on commit a21df0c

Please sign in to comment.