Browse Source

Remove use of WebClient#exchange from WebTestClient

The exchange() method is now deprecated because it is not safe for
general use but that doesn't apply to the WebTestClient because it
exposes a different higher level API for response handling that
ensures the response is consumed. Nevertheless WebTestClient cannot
call WebClient.exchange() any more.

To fix this WebTestClient no longer delegates to WebClient and thus
gains direct access to the underlying ExchangeFunction. This is
not a big deal because WebClient and WebTestClient do the same
only when it comes to gathering builder options and request input.

See gh-25751
pull/25823/head
Rossen Stoyanchev 5 years ago
parent
commit
db9190e0e6
  1. 201
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
  2. 229
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java
  3. 19
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
  4. 14
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java
  5. 2
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java
  6. 1
      spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt

201
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets; @@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -46,12 +47,17 @@ import org.springframework.test.util.AssertionErrors; @@ -46,12 +47,17 @@ import org.springframework.test.util.AssertionErrors;
import org.springframework.test.util.JsonExpectationsHelper;
import org.springframework.test.util.XmlExpectationsHelper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MimeType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebTestClient}.
@ -61,30 +67,42 @@ import org.springframework.web.util.UriBuilder; @@ -61,30 +67,42 @@ import org.springframework.web.util.UriBuilder;
*/
class DefaultWebTestClient implements WebTestClient {
private final WebClient webClient;
private final WiretapConnector wiretapConnector;
private final Duration timeout;
private final ExchangeFunction exchangeFunction;
private final UriBuilderFactory uriBuilderFactory;
@Nullable
private final HttpHeaders defaultHeaders;
@Nullable
private final MultiValueMap<String, String> defaultCookies;
private final Duration responseTimeout;
private final DefaultWebTestClientBuilder builder;
private final AtomicLong requestIndex = new AtomicLong();
DefaultWebTestClient(WebClient.Builder clientBuilder, ClientHttpConnector connector,
@Nullable Duration timeout, DefaultWebTestClientBuilder webTestClientBuilder) {
DefaultWebTestClient(ClientHttpConnector connector,
Function<ClientHttpConnector, ExchangeFunction> exchangeFactory, UriBuilderFactory uriBuilderFactory,
@Nullable HttpHeaders headers, @Nullable MultiValueMap<String, String> cookies,
@Nullable Duration responseTimeout, DefaultWebTestClientBuilder clientBuilder) {
Assert.notNull(clientBuilder, "WebClient.Builder is required");
this.wiretapConnector = new WiretapConnector(connector);
this.webClient = clientBuilder.clientConnector(this.wiretapConnector).build();
this.timeout = (timeout != null ? timeout : Duration.ofSeconds(5));
this.builder = webTestClientBuilder;
this.exchangeFunction = exchangeFactory.apply(this.wiretapConnector);
this.uriBuilderFactory = uriBuilderFactory;
this.defaultHeaders = headers;
this.defaultCookies = cookies;
this.responseTimeout = (responseTimeout != null ? responseTimeout : Duration.ofSeconds(5));
this.builder = clientBuilder;
}
private Duration getTimeout() {
return this.timeout;
private Duration getResponseTimeout() {
return this.responseTimeout;
}
@ -124,12 +142,12 @@ class DefaultWebTestClient implements WebTestClient { @@ -124,12 +142,12 @@ class DefaultWebTestClient implements WebTestClient {
}
@Override
public RequestBodyUriSpec method(HttpMethod method) {
return methodInternal(method);
public RequestBodyUriSpec method(HttpMethod httpMethod) {
return methodInternal(httpMethod);
}
private RequestBodyUriSpec methodInternal(HttpMethod method) {
return new DefaultRequestBodyUriSpec(this.webClient.method(method));
private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) {
return new DefaultRequestBodyUriSpec(httpMethod);
}
@Override
@ -145,154 +163,180 @@ class DefaultWebTestClient implements WebTestClient { @@ -145,154 +163,180 @@ class DefaultWebTestClient implements WebTestClient {
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
private final WebClient.RequestBodyUriSpec bodySpec;
private final HttpMethod httpMethod;
@Nullable
private URI uri;
private final HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private BodyInserter<?, ? super ClientHttpRequest> inserter;
private final Map<String, Object> attributes = new LinkedHashMap<>(4);
@Nullable
private Consumer<ClientHttpRequest> httpRequestConsumer;
@Nullable
private String uriTemplate;
private final String requestId;
DefaultRequestBodyUriSpec(WebClient.RequestBodyUriSpec spec) {
this.bodySpec = spec;
DefaultRequestBodyUriSpec(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
this.requestId = String.valueOf(requestIndex.incrementAndGet());
this.bodySpec.header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, this.requestId);
this.headers = new HttpHeaders();
this.headers.add(WebTestClient.WEBTESTCLIENT_REQUEST_ID, this.requestId);
}
@Override
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
this.bodySpec.uri(uriTemplate, uriVariables);
this.uriTemplate = uriTemplate;
return this;
return uri(uriBuilderFactory.expand(uriTemplate, uriVariables));
}
@Override
public RequestBodySpec uri(String uriTemplate, Map<String, ?> uriVariables) {
this.bodySpec.uri(uriTemplate, uriVariables);
this.uriTemplate = uriTemplate;
return this;
return uri(uriBuilderFactory.expand(uriTemplate, uriVariables));
}
@Override
public RequestBodySpec uri(Function<UriBuilder, URI> uriFunction) {
this.bodySpec.uri(uriFunction);
this.uriTemplate = null;
return this;
return uri(uriFunction.apply(uriBuilderFactory.builder()));
}
@Override
public RequestBodySpec uri(URI uri) {
this.bodySpec.uri(uri);
this.uriTemplate = null;
this.uri = uri;
return this;
}
private HttpHeaders getHeaders() {
return this.headers;
}
private MultiValueMap<String, String> getCookies() {
if (this.cookies == null) {
this.cookies = new LinkedMultiValueMap<>(3);
}
return this.cookies;
}
@Override
public RequestBodySpec header(String headerName, String... headerValues) {
this.bodySpec.header(headerName, headerValues);
for (String headerValue : headerValues) {
getHeaders().add(headerName, headerValue);
}
return this;
}
@Override
public RequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
this.bodySpec.headers(headersConsumer);
headersConsumer.accept(getHeaders());
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.bodySpec.attribute(name, value);
this.attributes.put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(
Consumer<Map<String, Object>> attributesConsumer) {
this.bodySpec.attributes(attributesConsumer);
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
attributesConsumer.accept(this.attributes);
return this;
}
@Override
public RequestBodySpec accept(MediaType... acceptableMediaTypes) {
this.bodySpec.accept(acceptableMediaTypes);
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
return this;
}
@Override
public RequestBodySpec acceptCharset(Charset... acceptableCharsets) {
this.bodySpec.acceptCharset(acceptableCharsets);
getHeaders().setAcceptCharset(Arrays.asList(acceptableCharsets));
return this;
}
@Override
public RequestBodySpec contentType(MediaType contentType) {
this.bodySpec.contentType(contentType);
getHeaders().setContentType(contentType);
return this;
}
@Override
public RequestBodySpec contentLength(long contentLength) {
this.bodySpec.contentLength(contentLength);
getHeaders().setContentLength(contentLength);
return this;
}
@Override
public RequestBodySpec cookie(String name, String value) {
this.bodySpec.cookie(name, value);
getCookies().add(name, value);
return this;
}
@Override
public RequestBodySpec cookies(
Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.bodySpec.cookies(cookiesConsumer);
public RequestBodySpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
cookiesConsumer.accept(getCookies());
return this;
}
@Override
public RequestBodySpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
this.bodySpec.ifModifiedSince(ifModifiedSince);
getHeaders().setIfModifiedSince(ifModifiedSince);
return this;
}
@Override
public RequestBodySpec ifNoneMatch(String... ifNoneMatches) {
this.bodySpec.ifNoneMatch(ifNoneMatches);
getHeaders().setIfNoneMatch(Arrays.asList(ifNoneMatches));
return this;
}
@Override
public RequestHeadersSpec<?> bodyValue(Object body) {
this.bodySpec.bodyValue(body);
this.inserter = BodyInserters.fromValue(body);
return this;
}
@Override
public <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass) {
this.bodySpec.body(publisher, elementClass);
public <T, P extends Publisher<T>> RequestHeadersSpec<?> body(
P publisher, ParameterizedTypeReference<T> elementTypeRef) {
this.inserter = BodyInserters.fromPublisher(publisher, elementTypeRef);
return this;
}
@Override
public <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, ParameterizedTypeReference<T> elementTypeRef) {
this.bodySpec.body(publisher, elementTypeRef);
public <T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, Class<T> elementClass) {
this.inserter = BodyInserters.fromPublisher(publisher, elementClass);
return this;
}
@Override
public RequestHeadersSpec<?> body(Object producer, Class<?> elementClass) {
this.bodySpec.body(producer, elementClass);
this.inserter = BodyInserters.fromProducer(producer, elementClass);
return this;
}
@Override
public RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementTypeRef) {
this.bodySpec.body(producer, elementTypeRef);
this.inserter = BodyInserters.fromProducer(producer, elementTypeRef);
return this;
}
@Override
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
this.bodySpec.body(inserter);
this.inserter = inserter;
return this;
}
@ -304,10 +348,57 @@ class DefaultWebTestClient implements WebTestClient { @@ -304,10 +348,57 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public ResponseSpec exchange() {
ClientResponse clientResponse = this.bodySpec.exchange().block(getTimeout());
Assert.state(clientResponse != null, "No ClientResponse");
ExchangeResult result = wiretapConnector.getExchangeResult(this.requestId, this.uriTemplate, getTimeout());
return new DefaultResponseSpec(result, clientResponse, getTimeout());
ClientRequest request = (this.inserter != null ?
initRequestBuilder().body(this.inserter).build() :
initRequestBuilder().build());
ClientResponse response = exchangeFunction.exchange(request).block(getResponseTimeout());
Assert.state(response != null, "No ClientResponse");
ExchangeResult result = wiretapConnector.getExchangeResult(
this.requestId, this.uriTemplate, getResponseTimeout());
return new DefaultResponseSpec(result, response, getResponseTimeout());
}
private ClientRequest.Builder initRequestBuilder() {
ClientRequest.Builder builder = ClientRequest.create(this.httpMethod, initUri())
.headers(headers -> headers.addAll(initHeaders()))
.cookies(cookies -> cookies.addAll(initCookies()))
.attributes(attributes -> attributes.putAll(this.attributes));
if (this.httpRequestConsumer != null) {
builder.httpRequest(this.httpRequestConsumer);
}
return builder;
}
private URI initUri() {
return (this.uri != null ? this.uri : uriBuilderFactory.expand(""));
}
private HttpHeaders initHeaders() {
if (CollectionUtils.isEmpty(defaultHeaders)) {
return this.headers;
}
HttpHeaders result = new HttpHeaders();
result.putAll(defaultHeaders);
result.putAll(this.headers);
return result;
}
private MultiValueMap<String, String> initCookies() {
if (CollectionUtils.isEmpty(this.cookies)) {
return (defaultCookies != null ? defaultCookies : new LinkedMultiValueMap<>());
}
else if (CollectionUtils.isEmpty(defaultCookies)) {
return this.cookies;
}
else {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
result.putAll(defaultCookies);
result.putAll(this.cookies);
return result;
}
}
}

229
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -17,20 +17,30 @@ @@ -17,20 +17,30 @@
package org.springframework.test.web.reactive.server;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
/**
@ -41,7 +51,21 @@ import org.springframework.web.util.UriBuilderFactory; @@ -41,7 +51,21 @@ import org.springframework.web.util.UriBuilderFactory;
*/
class DefaultWebTestClientBuilder implements WebTestClient.Builder {
private final WebClient.Builder webClientBuilder;
private static final boolean reactorClientPresent;
private static final boolean jettyClientPresent;
private static final boolean httpComponentsClientPresent;
static {
ClassLoader loader = DefaultWebTestClientBuilder.class.getClassLoader();
reactorClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader);
jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader);
httpComponentsClientPresent =
ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) &&
ClassUtils.isPresent("org.apache.hc.core5.reactive.ReactiveDataConsumer", loader);
}
@Nullable
private final WebHttpHandlerBuilder httpHandlerBuilder;
@ -49,136 +73,243 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -49,136 +73,243 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
@Nullable
private final ClientHttpConnector connector;
@Nullable
private String baseUrl;
@Nullable
private UriBuilderFactory uriBuilderFactory;
@Nullable
private HttpHeaders defaultHeaders;
@Nullable
private MultiValueMap<String, String> defaultCookies;
@Nullable
private List<ExchangeFilterFunction> filters;
@Nullable
private ExchangeStrategies strategies;
@Nullable
private List<Consumer<ExchangeStrategies.Builder>> strategiesConfigurers;
@Nullable
private Duration responseTimeout;
/** Connect to server via Reactor Netty. */
/** Determine connector via classpath detection. */
DefaultWebTestClientBuilder() {
this(new ReactorClientHttpConnector());
}
/** Connect to server through the given connector. */
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
this(null, null, connector, null);
this(null, null);
}
/** Connect to given mock server with mock request and response. */
/** Use HttpHandlerConnector with mock server. */
DefaultWebTestClientBuilder(WebHttpHandlerBuilder httpHandlerBuilder) {
this(null, httpHandlerBuilder, null, null);
this(httpHandlerBuilder, null);
}
/** Copy constructor. */
DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) {
this(other.webClientBuilder.clone(), other.httpHandlerBuilder, other.connector,
other.responseTimeout);
/** Use given connector. */
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
this(null, connector);
}
private DefaultWebTestClientBuilder(@Nullable WebClient.Builder webClientBuilder,
@Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector,
@Nullable Duration responseTimeout) {
DefaultWebTestClientBuilder(
@Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) {
Assert.isTrue(httpHandlerBuilder != null || connector != null,
"Either WebHttpHandlerBuilder or ClientHttpConnector must be provided");
Assert.isTrue(httpHandlerBuilder == null || connector == null,
"Expected WebHttpHandlerBuilder or ClientHttpConnector but not both.");
this.webClientBuilder = (webClientBuilder != null ? webClientBuilder : WebClient.builder());
this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.clone() : null);
this.connector = connector;
this.responseTimeout = responseTimeout;
this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.clone() : null);
}
/** Copy constructor. */
DefaultWebTestClientBuilder(DefaultWebTestClientBuilder other) {
this.httpHandlerBuilder = (other.httpHandlerBuilder != null ? other.httpHandlerBuilder.clone() : null);
this.connector = other.connector;
this.responseTimeout = other.responseTimeout;
this.baseUrl = other.baseUrl;
this.uriBuilderFactory = other.uriBuilderFactory;
if (other.defaultHeaders != null) {
this.defaultHeaders = new HttpHeaders();
this.defaultHeaders.putAll(other.defaultHeaders);
}
else {
this.defaultHeaders = null;
}
this.defaultCookies = (other.defaultCookies != null ?
new LinkedMultiValueMap<>(other.defaultCookies) : null);
this.filters = (other.filters != null ? new ArrayList<>(other.filters) : null);
this.strategies = other.strategies;
this.strategiesConfigurers = (other.strategiesConfigurers != null ?
new ArrayList<>(other.strategiesConfigurers) : null);
}
@Override
public WebTestClient.Builder baseUrl(String baseUrl) {
this.webClientBuilder.baseUrl(baseUrl);
this.baseUrl = baseUrl;
return this;
}
@Override
public WebTestClient.Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.webClientBuilder.uriBuilderFactory(uriBuilderFactory);
this.uriBuilderFactory = uriBuilderFactory;
return this;
}
@Override
public WebTestClient.Builder defaultHeader(String headerName, String... headerValues) {
this.webClientBuilder.defaultHeader(headerName, headerValues);
public WebTestClient.Builder defaultHeader(String header, String... values) {
initHeaders().put(header, Arrays.asList(values));
return this;
}
@Override
public WebTestClient.Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
this.webClientBuilder.defaultHeaders(headersConsumer);
headersConsumer.accept(initHeaders());
return this;
}
private HttpHeaders initHeaders() {
if (this.defaultHeaders == null) {
this.defaultHeaders = new HttpHeaders();
}
return this.defaultHeaders;
}
@Override
public WebTestClient.Builder defaultCookie(String cookieName, String... cookieValues) {
this.webClientBuilder.defaultCookie(cookieName, cookieValues);
public WebTestClient.Builder defaultCookie(String cookie, String... values) {
initCookies().addAll(cookie, Arrays.asList(values));
return this;
}
@Override
public WebTestClient.Builder defaultCookies(
Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.webClientBuilder.defaultCookies(cookiesConsumer);
public WebTestClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
cookiesConsumer.accept(initCookies());
return this;
}
private MultiValueMap<String, String> initCookies() {
if (this.defaultCookies == null) {
this.defaultCookies = new LinkedMultiValueMap<>(3);
}
return this.defaultCookies;
}
@Override
public WebTestClient.Builder filter(ExchangeFilterFunction filter) {
this.webClientBuilder.filter(filter);
Assert.notNull(filter, "ExchangeFilterFunction must not be null");
initFilters().add(filter);
return this;
}
@Override
public WebTestClient.Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer) {
this.webClientBuilder.filters(filtersConsumer);
filtersConsumer.accept(initFilters());
return this;
}
private List<ExchangeFilterFunction> initFilters() {
if (this.filters == null) {
this.filters = new ArrayList<>();
}
return this.filters;
}
@Override
public WebTestClient.Builder codecs(Consumer<ClientCodecConfigurer> configurer) {
this.webClientBuilder.codecs(configurer);
if (this.strategiesConfigurers == null) {
this.strategiesConfigurers = new ArrayList<>(4);
}
this.strategiesConfigurers.add(builder -> builder.codecs(configurer));
return this;
}
@Override
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
this.webClientBuilder.exchangeStrategies(strategies);
this.strategies = strategies;
return this;
}
@SuppressWarnings("deprecation")
@Override
@SuppressWarnings("deprecation")
public WebTestClient.Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer) {
this.webClientBuilder.exchangeStrategies(configurer);
if (this.strategiesConfigurers == null) {
this.strategiesConfigurers = new ArrayList<>(4);
}
this.strategiesConfigurers.add(configurer);
return this;
}
@Override
public WebTestClient.Builder responseTimeout(Duration timeout) {
this.responseTimeout = timeout;
public WebTestClient.Builder apply(WebTestClientConfigurer configurer) {
configurer.afterConfigurerAdded(this, this.httpHandlerBuilder, this.connector);
return this;
}
@Override
public WebTestClient.Builder apply(WebTestClientConfigurer configurer) {
configurer.afterConfigurerAdded(this, this.httpHandlerBuilder, this.connector);
public WebTestClient.Builder responseTimeout(Duration timeout) {
this.responseTimeout = timeout;
return this;
}
@Override
public WebTestClient build() {
ClientHttpConnector connectorToUse = this.connector;
if (connectorToUse == null) {
Assert.state(this.httpHandlerBuilder != null, "No WebHttpHandlerBuilder available");
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build());
if (this.httpHandlerBuilder != null) {
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build());
}
}
if (connectorToUse == null) {
connectorToUse = initConnector();
}
Function<ClientHttpConnector, ExchangeFunction> exchangeFactory = connector -> {
ExchangeFunction exchange = ExchangeFunctions.create(connector, initExchangeStrategies());
if (CollectionUtils.isEmpty(this.filters)) {
return exchange;
}
return this.filters.stream()
.reduce(ExchangeFilterFunction::andThen)
.map(filter -> filter.apply(exchange))
.orElse(exchange);
};
return new DefaultWebTestClient(connectorToUse, exchangeFactory, initUriBuilderFactory(),
this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null,
this.responseTimeout, new DefaultWebTestClientBuilder(this));
}
private static ClientHttpConnector initConnector() {
if (reactorClientPresent) {
return new ReactorClientHttpConnector();
}
else if (jettyClientPresent) {
return new JettyClientHttpConnector();
}
else if (httpComponentsClientPresent) {
return new HttpComponentsClientHttpConnector();
}
throw new IllegalStateException("No suitable default ClientHttpConnector found");
}
return new DefaultWebTestClient(this.webClientBuilder,
connectorToUse, this.responseTimeout, new DefaultWebTestClientBuilder(this));
private ExchangeStrategies initExchangeStrategies() {
if (CollectionUtils.isEmpty(this.strategiesConfigurers)) {
return (this.strategies != null ? this.strategies : ExchangeStrategies.withDefaults());
}
ExchangeStrategies.Builder builder =
(this.strategies != null ? this.strategies.mutate() : ExchangeStrategies.builder());
this.strategiesConfigurers.forEach(configurer -> configurer.accept(builder));
return builder.build();
}
private UriBuilderFactory initUriBuilderFactory() {
if (this.uriBuilderFactory != null) {
return this.uriBuilderFactory;
}
return (this.baseUrl != null ?
new DefaultUriBuilderFactory(this.baseUrl) : new DefaultUriBuilderFactory());
}
}

19
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

@ -49,7 +49,6 @@ import org.springframework.util.LinkedMultiValueMap; @@ -49,7 +49,6 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
@ -85,12 +84,12 @@ class DefaultWebClient implements WebClient { @@ -85,12 +84,12 @@ class DefaultWebClient implements WebClient {
private final DefaultWebClientBuilder builder;
DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable UriBuilderFactory factory,
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory uriBuilderFactory,
@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> defaultCookies,
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest, DefaultWebClientBuilder builder) {
this.exchangeFunction = exchangeFunction;
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
this.uriBuilderFactory = uriBuilderFactory;
this.defaultHeaders = defaultHeaders;
this.defaultCookies = defaultCookies;
this.defaultRequest = defaultRequest;
@ -251,13 +250,6 @@ class DefaultWebClient implements WebClient { @@ -251,13 +250,6 @@ class DefaultWebClient implements WebClient {
return this;
}
@Override
public RequestBodySpec httpRequest(Consumer<ClientHttpRequest> requestConsumer) {
this.httpRequestConsumer = (this.httpRequestConsumer != null ?
this.httpRequestConsumer.andThen(requestConsumer) : requestConsumer);
return this;
}
@Override
public DefaultRequestBodyUriSpec accept(MediaType... acceptableMediaTypes) {
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
@ -306,6 +298,13 @@ class DefaultWebClient implements WebClient { @@ -306,6 +298,13 @@ class DefaultWebClient implements WebClient {
return this;
}
@Override
public RequestBodySpec httpRequest(Consumer<ClientHttpRequest> requestConsumer) {
this.httpRequestConsumer = (this.httpRequestConsumer != null ?
this.httpRequestConsumer.andThen(requestConsumer) : requestConsumer);
return this;
}
@Override
public RequestHeadersSpec<?> bodyValue(Object body) {
this.inserter = BodyInserters.fromValue(body);

14
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java

@ -262,24 +262,26 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -262,24 +262,26 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient build() {
ClientHttpConnector connectorToUse =
(this.connector != null ? this.connector : initConnector());
ExchangeFunction exchange = (this.exchangeFunction == null ?
ExchangeFunctions.create(getOrInitConnector(), initExchangeStrategies()) :
ExchangeFunctions.create(connectorToUse, initExchangeStrategies()) :
this.exchangeFunction);
ExchangeFunction filteredExchange = (this.filters != null ? this.filters.stream()
.reduce(ExchangeFilterFunction::andThen)
.map(filter -> filter.apply(exchange))
.orElse(exchange) : exchange);
return new DefaultWebClient(filteredExchange, initUriBuilderFactory(),
this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null,
this.defaultRequest, new DefaultWebClientBuilder(this));
}
private ClientHttpConnector getOrInitConnector() {
if (this.connector != null) {
return this.connector;
}
else if (reactorClientPresent) {
private ClientHttpConnector initConnector() {
if (reactorClientPresent) {
return new ReactorClientHttpConnector();
}
else if (jettyClientPresent) {

2
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java

@ -43,11 +43,11 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -43,11 +43,11 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.when;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link DefaultWebClient}.

1
spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt

@ -80,6 +80,7 @@ class WebClientExtensionsTests { @@ -80,6 +80,7 @@ class WebClientExtensionsTests {
}
@Test
@Suppress("DEPRECATION")
fun awaitExchange() {
val response = mockk<ClientResponse>()
every { requestBodySpec.exchange() } returns Mono.just(response)

Loading…
Cancel
Save