diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index f314b4455daf..4d5aeca17eb7 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -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. @@ -137,11 +137,24 @@ public WebTestClient.Builder filters(Consumer> 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 configurer) { + this.webClientBuilder.exchangeStrategies(configurer); + return this; + } + @Override public WebTestClient.Builder responseTimeout(Duration timeout) { this.responseTimeout = timeout; diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index cc2e8ab96fc8..254d0337fd04 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -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 @@ -436,11 +437,34 @@ interface Builder { /** * Configure the {@link ExchangeStrategies} to use. - *

By default {@link ExchangeStrategies#withDefaults()} is used. + *

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. + *

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}. + *

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 configurer); + /** * Max amount of time to wait for responses. *

By default 5 seconds. @@ -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); } @@ -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 namespaces, Object... args); + XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args); /** * Assert the response body content with the given {@link Consumer}. diff --git a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java index db31e97218a4..028d85af381b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ClientCodecConfigurer.java @@ -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. @@ -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}. diff --git a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java index 3d4c625b5d56..bd573d08bae3 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java @@ -87,6 +87,12 @@ public interface CodecConfigurer { */ List> getWriters(); + /** + * Clone this {@link CodecConfigurer}. + * @since 5.1.12 + */ + CodecConfigurer clone(); + /** * Customize or replace the HTTP message readers and writers registered by diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java index e86ac954f9f6..f67821f98b72 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseCodecConfigurer.java @@ -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; /** @@ -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); } @@ -87,6 +98,17 @@ public List> 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"), @@ -110,7 +132,7 @@ protected List> 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> typedReaders = new ArrayList<>(); @@ -121,6 +143,16 @@ private static final class DefaultCustomCodecs implements CustomCodecs { private final List> 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)); @@ -143,7 +175,6 @@ public void writer(HttpMessageWriter writer) { (canWriteObject ? this.objectWriters : this.typedWriters).add(writer); } - // Package private accessors... List> getTypedReaders() { diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java index f3034ad9354c..5cf59998af2a 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java @@ -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; diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java index 9f578b7320ab..e764cb969612 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/ClientDefaultCodecsImpl.java @@ -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. @@ -49,6 +49,17 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo private Supplier>> 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. @@ -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> objectReaders) { @@ -116,6 +135,17 @@ private static class DefaultMultipartCodecs implements ClientCodecConfigurer.Mul private final List> 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)); diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java index 9875ded1b98d..737282eecd5e 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultClientCodecConfigurer.java @@ -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. @@ -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()); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java index 2623d5a7f7b2..661d45d66693 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/DefaultServerCodecConfigurer.java @@ -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. @@ -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()); + } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java index 37e924cd7e91..1d997c3777b1 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java +++ b/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java @@ -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; diff --git a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java index 48e20a9074da..93d981c713e8 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/support/CodecConfigurerTests.java @@ -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> readers) { HttpMessageReader reader = readers.get(this.index.getAndIncrement()); assertEquals(DecoderHttpMessageReader.class, reader.getClass()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java index aa1523d9ace5..02b0cc5e5587 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultExchangeStrategiesBuilder.java @@ -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. @@ -42,13 +42,18 @@ final class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Build } - private final ClientCodecConfigurer codecConfigurer = ClientCodecConfigurer.create(); + private final ClientCodecConfigurer codecConfigurer; public DefaultExchangeStrategiesBuilder() { + this.codecConfigurer = ClientCodecConfigurer.create(); this.codecConfigurer.registerDefaults(false); } + private DefaultExchangeStrategiesBuilder(DefaultExchangeStrategies other) { + this.codecConfigurer = other.codecConfigurer.clone(); + } + public void defaultConfiguration() { this.codecConfigurer.registerDefaults(true); @@ -62,21 +67,23 @@ public ExchangeStrategies.Builder codecs(Consumer consume @Override public ExchangeStrategies build() { - return new DefaultExchangeStrategies( - this.codecConfigurer.getReaders(), this.codecConfigurer.getWriters()); + return new DefaultExchangeStrategies(this.codecConfigurer); } private static class DefaultExchangeStrategies implements ExchangeStrategies { + private final ClientCodecConfigurer codecConfigurer; + private final List> readers; private final List> writers; - public DefaultExchangeStrategies(List> readers, List> writers) { - this.readers = unmodifiableCopy(readers); - this.writers = unmodifiableCopy(writers); + public DefaultExchangeStrategies(ClientCodecConfigurer codecConfigurer) { + this.codecConfigurer = codecConfigurer; + this.readers = unmodifiableCopy(this.codecConfigurer.getReaders()); + this.writers = unmodifiableCopy(this.codecConfigurer.getWriters()); } private static List unmodifiableCopy(List list) { @@ -84,6 +91,12 @@ private static List unmodifiableCopy(List list) { } + @Override + @Deprecated + public Builder mutate() { + return new DefaultExchangeStrategiesBuilder(this); + } + @Override public List> messageReaders() { return this.readers; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index c8796e91f782..6f9a755a7fa7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -38,6 +38,7 @@ * Default implementation of {@link WebClient.Builder}. * * @author Rossen Stoyanchev + * @author Brian Clozel * @since 5.0 */ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -66,14 +67,16 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @Nullable private ClientHttpConnector connector; - private ExchangeStrategies exchangeStrategies; + @Nullable + private ExchangeStrategies.Builder exchangeStrategies; + + private List> strategiesConfigurers; @Nullable private ExchangeFunction exchangeFunction; public DefaultWebClientBuilder() { - this.exchangeStrategies = ExchangeStrategies.withDefaults(); } public DefaultWebClientBuilder(DefaultWebClientBuilder other) { @@ -190,12 +193,26 @@ public WebClient.Builder clientConnector(ClientHttpConnector connector) { } @Override + @Deprecated public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) { + Assert.notNull(strategies, "ExchangeStrategies must not be null"); + this.exchangeStrategies = strategies.mutate(); + return this; + } + + @Override + public WebClient.Builder exchangeStrategies(ExchangeStrategies.Builder strategies) { Assert.notNull(strategies, "ExchangeStrategies must not be null"); this.exchangeStrategies = strategies; return this; } + @Override + public WebClient.Builder exchangeStrategies(Consumer configurer) { + this.strategiesConfigurers.add(configurer); + return this; + } + @Override public WebClient.Builder exchangeFunction(ExchangeFunction exchangeFunction) { this.exchangeFunction = exchangeFunction; @@ -231,11 +248,24 @@ private ExchangeFunction initExchangeFunction() { return this.exchangeFunction; } else if (this.connector != null) { - return ExchangeFunctions.create(this.connector, this.exchangeStrategies); + return ExchangeFunctions.create(this.connector, initExchangeStrategies()); } else { - return ExchangeFunctions.create(new ReactorClientHttpConnector(), this.exchangeStrategies); + return ExchangeFunctions.create(new ReactorClientHttpConnector(), initExchangeStrategies()); + } + } + + @SuppressWarnings("deprecation") + private ExchangeStrategies initExchangeStrategies() { + if (CollectionUtils.isEmpty(this.strategiesConfigurers)) { + return this.exchangeStrategies != null ? this.exchangeStrategies.build() : ExchangeStrategies.withDefaults(); } + + ExchangeStrategies.Builder builder = + this.exchangeStrategies != null ? this.exchangeStrategies : ExchangeStrategies.builder(); + + this.strategiesConfigurers.forEach(configurer -> configurer.accept(builder)); + return builder.build(); } private UriBuilderFactory initUriBuilderFactory() { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java index 804fbd9a42fd..dfc2e1e14d59 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeStrategies.java @@ -47,6 +47,18 @@ public interface ExchangeStrategies { */ List> messageWriters(); + /** + * Return a builder to create a new {@link ExchangeStrategies} instance + * replicated from the current instance. + * @since 5.1.12 + * @deprecated APIs should consume {@link ExchangeStrategies} as final or accept an + * {@link ExchangeStrategies.Builder builder}. + */ + @Deprecated + default Builder mutate() { + throw new UnsupportedOperationException("This ExchangeStrategies implementation does not support mutation."); + } + // Static builder methods diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 8dc2a17c0127..91a45fe06480 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -64,6 +64,7 @@ * * @author Rossen Stoyanchev * @author Arjen Poutsma + * @author Brian Clozel * @since 5.0 */ public interface WebClient { @@ -288,12 +289,35 @@ interface Builder { Builder clientConnector(ClientHttpConnector connector); /** - * Configure the {@link ExchangeStrategies} to use. - *

By default this is obtained from {@link ExchangeStrategies#withDefaults()}. + * Provide the {@link ExchangeStrategies} to use. + *

This is useful for changing the default settings, yet still allowing + * further customizations via {@link #exchangeStrategies(Consumer)}. + * If not set, defaults are obtained from {@link ExchangeStrategies#withDefaults()}. * @param strategies the strategies to use + * @deprecated as of 5.1, in favor of {@link #exchangeStrategies(ExchangeStrategies.Builder)} */ + @Deprecated Builder exchangeStrategies(ExchangeStrategies strategies); + /** + * Provide the {@link ExchangeStrategies.Builder} to use. + *

This is useful for changing the default settings, yet still allowing + * further customizations via {@link #exchangeStrategies(Consumer)}. + * If not set, defaults are obtained from {@link ExchangeStrategies#builder()}. + * @param strategies the strategies to use + * @since 5.1.12 + */ + Builder exchangeStrategies(ExchangeStrategies.Builder strategies); + + /** + * Customize the {@link ExchangeStrategies}. + *

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 configurer); + /** * Provide an {@link ExchangeFunction} pre-configured with * {@link ClientHttpConnector} and {@link ExchangeStrategies}. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java index b08662c8fb9c..09f7cb24ef7c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeStrategiesTests.java @@ -39,4 +39,15 @@ public void withDefaults() { assertFalse(strategies.messageWriters().isEmpty()); } + @Test + @SuppressWarnings("deprecation") + public void mutate() { + ExchangeStrategies strategies = ExchangeStrategies.empty().build(); + assertTrue(strategies.messageReaders().isEmpty()); + assertTrue(strategies.messageWriters().isEmpty()); + ExchangeStrategies mutated = strategies.mutate().codecs(codecs -> codecs.registerDefaults(true)).build(); + assertFalse(mutated.messageReaders().isEmpty()); + assertFalse(mutated.messageWriters().isEmpty()); + } + } diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 20980df9dd85..7f6523fa3b1e 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -42,14 +42,14 @@ The following example configures < { - // ... - }) - .build(); + Consumer customizeCodecs = builder -> { + builder.codecs(configurer -> { + //... + }); + }; WebClient client = WebClient.builder() - .exchangeStrategies(strategies) + .exchangeStrategies(customizeCodecs) .build(); ---- ==== @@ -73,7 +73,35 @@ modified copy without affecting the original instance, as the following example ---- ==== +[[webflux-client-builder-maxinmemorysize]] +=== MaxInMemorySize + +Spring WebFlux configures by default a maximum size for buffering data in-memory when decoding +HTTP responses with the `WebClient`. This avoids application memory issues if the received +response is much larger than expected. + +You can configure a default value that might not be enough for your use case, and your application +can hit that limit with the following: + +---- +org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer +---- +You can configure this limit on all default codecs with the following code sample: + +==== +[source,java,intent=0] +[subs="verbatim,quotes"] +---- + WebClient webClient = WebClient.builder() + .exchangeStrategies(configurer -> + configurer.codecs(codecs -> + codecs.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) + ) + ) + .build(); +---- +==== [[webflux-client-builder-reactor]] === Reactor Netty