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-23961
  • Loading branch information
bclozel committed Nov 29, 2019
1 parent d7023fd commit b3020bc
Show file tree
Hide file tree
Showing 18 changed files with 345 additions and 46 deletions.
Expand Up @@ -169,18 +169,19 @@ interface Builder {
RSocketRequester.Builder setupMetadata(Object value, @Nullable MimeType mimeType);

/**
* Provide {@link RSocketStrategies} to use.
* <p>By default this is based on default settings of
* {@link RSocketStrategies.Builder} but may be further customized via
* {@link #rsocketStrategies(Consumer)}.
* Provide the {@link RSocketStrategies} to use.
* <p>This is useful for changing the default settings, yet still allowing
* further customizations via {@link #rsocketStrategies(Consumer)}.
* If not set, defaults are obtained from {@link RSocketStrategies#builder()}.
* @param strategies the strategies to use
*/
RSocketRequester.Builder rsocketStrategies(@Nullable RSocketStrategies strategies);

/**
* Customize the {@link RSocketStrategies}.
* <p>By default this starts out as {@link RSocketStrategies#builder()}.
* However if strategies were {@link #rsocketStrategies(RSocketStrategies) set}
* explicitly, then they are {@link RSocketStrategies#mutate() mutated}.
* <p>Allows further customization on {@link RSocketStrategies},
* mutating them if they were {@link #rsocketStrategies(RSocketStrategies) set},
* or starting from {@link RSocketStrategies#builder()} defaults}.
*/
RSocketRequester.Builder rsocketStrategies(Consumer<RSocketStrategies.Builder> configurer);

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 @@ -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 @@ -84,6 +84,7 @@
* 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 @@ -443,11 +444,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 @@ -928,7 +952,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 @@ -942,7 +966,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 @@ -106,6 +106,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());
}
}

0 comments on commit b3020bc

Please sign in to comment.