Skip to content

Commit

Permalink
Allow ExchangeStrategies customizations in WebClient
Browse files Browse the repository at this point in the history
Prior to this commit, developers could configure their WebClient to use
their custom `ExchangeStrategies`, by providing it in the
`WebClient.Builder` chain.
Once created, an `ExchangeStrategies` instance is not mutable, which
makes it hard for further customizations by other components. In the
case of the reported issue, other components would override the default
configuration for the codecs maxInMemorySize.

This commit makes the `ExchangeStrategies` mutable and uses that fact to
further customize them with a new `WebClient.Builder#exchangeStrategies`
`Consumer` variant. This commit is also deprecating those mutating
variants in favor of a new `WebClient.Builder#exchangeStrategies` that
takes a `ExchangeStrategies#Builder` directly and avoids mutation issues
altogether.

Closes gh-24106
  • Loading branch information
bclozel committed Nov 29, 2019
1 parent 7fdf775 commit 43e047c
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 31 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -137,11 +137,24 @@ public WebTestClient.Builder filters(Consumer<List<ExchangeFilterFunction>> filt
}

@Override
@Deprecated
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
this.webClientBuilder.exchangeStrategies(strategies);
return this;
}

@Override
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies.Builder strategies) {
this.webClientBuilder.exchangeStrategies(strategies);
return this;
}

@Override
public WebTestClient.Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer) {
this.webClientBuilder.exchangeStrategies(configurer);
return this;
}

@Override
public WebTestClient.Builder responseTimeout(Duration timeout) {
this.responseTimeout = timeout;
Expand Down
Expand Up @@ -77,6 +77,7 @@
* and Spring Kotlin extensions to perform integration tests on an embedded WebFlux server.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
* @see StatusAssertions
* @see HeaderAssertions
Expand Down Expand Up @@ -436,11 +437,34 @@ interface Builder {

/**
* Configure the {@link ExchangeStrategies} to use.
* <p>By default {@link ExchangeStrategies#withDefaults()} is used.
* <p>This is useful for changing the default settings, yet still allowing
* further customizations via {@link #exchangeStrategies(Consumer)}.
* By default {@link ExchangeStrategies#withDefaults()} is used.
* @param strategies the strategies to use
* @deprecated as of 5.1 in favor of {@link #exchangeStrategies(ExchangeStrategies.Builder)}
*/
@Deprecated
Builder exchangeStrategies(ExchangeStrategies strategies);

/**
* Configure the {@link ExchangeStrategies.Builder} to use.
* <p>This is useful for changing the default settings, yet still allowing
* further customizations via {@link #exchangeStrategies(Consumer)}.
* By default {@link ExchangeStrategies#builder()} is used.
* @param strategies the strategies to use
* @since 5.1.12
*/
Builder exchangeStrategies(ExchangeStrategies.Builder strategies);

/**
* Customize the {@link ExchangeStrategies}.
* <p>Allows further customization on {@link ExchangeStrategies},
* mutating them if they were {@link #exchangeStrategies(ExchangeStrategies) set},
* or starting from {@link ExchangeStrategies#withDefaults() defaults}.
* @since 5.1.12
*/
Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer);

/**
* Max amount of time to wait for responses.
* <p>By default 5 seconds.
Expand Down Expand Up @@ -877,7 +901,7 @@ interface BodyContentSpec {
* @since 5.1
* @see #xpath(String, Map, Object...)
*/
default XpathAssertions xpath(String expression, Object... args){
default XpathAssertions xpath(String expression, Object... args) {
return xpath(expression, null, args);
}

Expand All @@ -891,7 +915,7 @@ default XpathAssertions xpath(String expression, Object... args){
* @param args arguments to parameterize the expression
* @since 5.1
*/
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);
XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args);

/**
* Assert the response body content with the given {@link Consumer}.
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -63,6 +63,11 @@ public interface ClientCodecConfigurer extends CodecConfigurer {
@Override
ClientDefaultCodecs defaultCodecs();

/**
* Clone this {@link ClientCodecConfigurer}.
*/
@Override
ClientCodecConfigurer clone();

/**
* Static factory method for a {@code ClientCodecConfigurer}.
Expand Down
Expand Up @@ -87,6 +87,12 @@ public interface CodecConfigurer {
*/
List<HttpMessageWriter<?>> getWriters();

/**
* Clone this {@link CodecConfigurer}.
* @since 5.1.12
*/
CodecConfigurer clone();


/**
* Customize or replace the HTTP message readers and writers registered by
Expand Down
Expand Up @@ -34,13 +34,14 @@
* client and server specific variants.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
*/
class BaseCodecConfigurer implements CodecConfigurer {

private final BaseDefaultCodecs defaultCodecs;
protected final BaseDefaultCodecs defaultCodecs;

private final DefaultCustomCodecs customCodecs = new DefaultCustomCodecs();
protected final DefaultCustomCodecs customCodecs;


/**
Expand All @@ -50,6 +51,16 @@ class BaseCodecConfigurer implements CodecConfigurer {
BaseCodecConfigurer(BaseDefaultCodecs defaultCodecs) {
Assert.notNull(defaultCodecs, "'defaultCodecs' is required");
this.defaultCodecs = defaultCodecs;
this.customCodecs = new DefaultCustomCodecs();
}

/**
* Constructor with another {@link BaseCodecConfigurer} to copy
* the configuration from.
*/
BaseCodecConfigurer(BaseCodecConfigurer other) {
this.defaultCodecs = other.cloneDefaultCodecs();
this.customCodecs = new DefaultCustomCodecs(other.customCodecs);
}


Expand Down Expand Up @@ -87,6 +98,17 @@ public List<HttpMessageWriter<?>> getWriters() {
return getWritersInternal(false);
}


@Override
public CodecConfigurer clone() {
return new BaseCodecConfigurer(this);
}

protected BaseDefaultCodecs cloneDefaultCodecs() {
return new BaseDefaultCodecs(this.defaultCodecs);
}


/**
* Internal method that returns the configured writers.
* @param forMultipart whether to returns writers for general use ("false"),
Expand All @@ -110,7 +132,7 @@ protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
/**
* Default implementation of {@code CustomCodecs}.
*/
private static final class DefaultCustomCodecs implements CustomCodecs {
protected static final class DefaultCustomCodecs implements CustomCodecs {

private final List<HttpMessageReader<?>> typedReaders = new ArrayList<>();

Expand All @@ -121,6 +143,16 @@ private static final class DefaultCustomCodecs implements CustomCodecs {
private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();


DefaultCustomCodecs() {
}

DefaultCustomCodecs(DefaultCustomCodecs other) {
other.typedReaders.addAll(this.typedReaders);
other.typedWriters.addAll(this.typedWriters);
other.objectReaders.addAll(this.objectReaders);
other.objectWriters.addAll(this.objectWriters);
}

@Override
public void decoder(Decoder<?> decoder) {
reader(new DecoderHttpMessageReader<>(decoder));
Expand All @@ -143,7 +175,6 @@ public void writer(HttpMessageWriter<?> writer) {
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
}


// Package private accessors...

List<HttpMessageReader<?>> getTypedReaders() {
Expand Down
Expand Up @@ -105,6 +105,21 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
private boolean registerDefaults = true;


BaseDefaultCodecs() {
}

protected BaseDefaultCodecs(BaseDefaultCodecs other) {
this.jackson2JsonDecoder = other.jackson2JsonDecoder;
this.jackson2JsonEncoder = other.jackson2JsonEncoder;
this.protobufDecoder = other.protobufDecoder;
this.protobufEncoder = other.protobufEncoder;
this.jaxb2Decoder = other.jaxb2Decoder;
this.jaxb2Encoder = other.jaxb2Encoder;
this.maxInMemorySize = other.maxInMemorySize;
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
this.registerDefaults = other.registerDefaults;
}

@Override
public void jackson2JsonDecoder(Decoder<?> decoder) {
this.jackson2JsonDecoder = decoder;
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -49,6 +49,17 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
private Supplier<List<HttpMessageWriter<?>>> partWritersSupplier;


ClientDefaultCodecsImpl() {
}

ClientDefaultCodecsImpl(ClientDefaultCodecsImpl other) {
super(other);
this.multipartCodecs = new DefaultMultipartCodecs(other.multipartCodecs);
this.sseDecoder = other.sseDecoder;
this.partWritersSupplier = other.partWritersSupplier;
}


/**
* Set a supplier for part writers to use when
* {@link #multipartCodecs()} are not explicitly configured.
Expand All @@ -73,6 +84,14 @@ public void serverSentEventDecoder(Decoder<?> decoder) {
this.sseDecoder = decoder;
}

@Override
public ClientDefaultCodecsImpl clone() {
ClientDefaultCodecsImpl codecs = new ClientDefaultCodecsImpl();
codecs.multipartCodecs = this.multipartCodecs;
codecs.sseDecoder = this.sseDecoder;
codecs.partWritersSupplier = this.partWritersSupplier;
return codecs;
}

@Override
protected void extendObjectReaders(List<HttpMessageReader<?>> objectReaders) {
Expand Down Expand Up @@ -116,6 +135,17 @@ private static class DefaultMultipartCodecs implements ClientCodecConfigurer.Mul

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


DefaultMultipartCodecs() {
}

DefaultMultipartCodecs(@Nullable DefaultMultipartCodecs other) {
if (other != null) {
this.writers.addAll(other.writers);
}
}


@Override
public ClientCodecConfigurer.MultipartCodecs encoder(Encoder<?> encoder) {
writer(new EncoderHttpMessageWriter<>(encoder));
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,14 +26,30 @@
*/
public class DefaultClientCodecConfigurer extends BaseCodecConfigurer implements ClientCodecConfigurer {


public DefaultClientCodecConfigurer() {
super(new ClientDefaultCodecsImpl());
((ClientDefaultCodecsImpl) defaultCodecs()).setPartWritersSupplier(() -> getWritersInternal(true));
}

private DefaultClientCodecConfigurer(DefaultClientCodecConfigurer other) {
super(other);
}


@Override
public ClientDefaultCodecs defaultCodecs() {
return (ClientDefaultCodecs) super.defaultCodecs();
}

@Override
public DefaultClientCodecConfigurer clone() {
return new DefaultClientCodecConfigurer(this);
}

@Override
protected BaseDefaultCodecs cloneDefaultCodecs() {
return new ClientDefaultCodecsImpl((ClientDefaultCodecsImpl) defaultCodecs());
}

}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,13 +26,28 @@
*/
public class DefaultServerCodecConfigurer extends BaseCodecConfigurer implements ServerCodecConfigurer {


public DefaultServerCodecConfigurer() {
super(new ServerDefaultCodecsImpl());
}

private DefaultServerCodecConfigurer(BaseCodecConfigurer other) {
super(other);
}


@Override
public ServerDefaultCodecs defaultCodecs() {
return (ServerDefaultCodecs) super.defaultCodecs();
}

@Override
public DefaultServerCodecConfigurer clone() {
return new DefaultServerCodecConfigurer(this);
}

@Override
protected BaseDefaultCodecs cloneDefaultCodecs() {
return new ServerDefaultCodecsImpl((ServerDefaultCodecsImpl) defaultCodecs());
}
}
Expand Up @@ -46,6 +46,16 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
private Encoder<?> sseEncoder;


ServerDefaultCodecsImpl() {
}

ServerDefaultCodecsImpl(ServerDefaultCodecsImpl other) {
super(other);
this.multipartReader = other.multipartReader;
this.sseEncoder = other.sseEncoder;
}


@Override
public void multipartReader(HttpMessageReader<?> reader) {
this.multipartReader = reader;
Expand Down
Expand Up @@ -267,6 +267,14 @@ public void encoderDecoderOverrides() {
assertEncoderInstance(jaxb2Encoder);
}

@Test
public void cloneConfigurer() {
CodecConfigurer clone = this.configurer.clone();
this.configurer.registerDefaults(false);
assertEquals(0, this.configurer.getReaders().size());
assertEquals(11, clone.getReaders().size());
}

private Decoder<?> getNextDecoder(List<HttpMessageReader<?>> readers) {
HttpMessageReader<?> reader = readers.get(this.index.getAndIncrement());
assertEquals(DecoderHttpMessageReader.class, reader.getClass());
Expand Down

0 comments on commit 43e047c

Please sign in to comment.