Rename WebClientOperations to WebClient

This commit makes the following changes:

 - Merge WebClient into ExchangeFunction. Static methods on WebClient
have been moved to the utility class ExchangeFunctions, similar to
RouterFunctions operates on the server side.

 - Renamed WebClientOperations to WebClient.

 - Renamed WebClientStrategies to ExchangeStrategies
This commit is contained in:
Arjen Poutsma
2017-01-31 12:55:54 +01:00
parent 60517b23e2
commit 52e87cb425
20 changed files with 500 additions and 587 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -30,12 +30,12 @@ import org.springframework.web.reactive.function.BodyInserter;
/**
* Represents a typed, immutable, client-side HTTP request, as executed by the
* {@link WebClient}. Instances of this interface can be created via static
* builder methods in this class.
* {@link ExchangeFunction}. Instances of this interface can be created via static
* builder methods.
*
* <p>Note that applications are more likely to perform requests through
* {@link WebClientOperations} rather than using this directly.
* :
* {@link WebClient} rather than using this directly.
*
* @param <T> the type of the body that this request contains
* @author Brian Clozel
* @author Arjen Poutsma
@@ -75,7 +75,7 @@ public interface ClientRequest<T> {
* @param strategies the strategies to use when writing
* @return {@code Mono<Void>} to indicate when writing is complete
*/
Mono<Void> writeTo(ClientHttpRequest request, WebClientStrategies strategies);
Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies);
// Static builder methods

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -30,7 +30,7 @@ import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.web.reactive.function.BodyExtractor;
/**
* Represents an HTTP response, as returned by the {@link WebClient}.
* Represents an HTTP response, as returned by the {@link ExchangeFunction}.
* Access to headers and body is offered by {@link Headers} and
* {@link #body(BodyExtractor)}, {@link #bodyToMono(Class)}, {@link #bodyToFlux(Class)}
* respectively.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -154,7 +154,7 @@ class DefaultClientRequestBuilder implements ClientRequest.Builder {
}
@Override
public Mono<Void> writeTo(ClientHttpRequest request, WebClientStrategies strategies) {
public Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies) {
HttpHeaders requestHeaders = request.getHeaders();
if (!this.headers.isEmpty()) {
this.headers.entrySet().stream()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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,10 +49,10 @@ class DefaultClientResponse implements ClientResponse {
private final Headers headers;
private final WebClientStrategies strategies;
private final ExchangeStrategies strategies;
public DefaultClientResponse(ClientHttpResponse response, WebClientStrategies strategies) {
public DefaultClientResponse(ClientHttpResponse response, ExchangeStrategies strategies) {
this.response = response;
this.strategies = strategies;
this.headers = new DefaultHeaders();

View File

@@ -46,22 +46,22 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Default implementation of {@link WebClientStrategies.Builder}.
* Default implementation of {@link ExchangeStrategies.Builder}.
*
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultWebClientStrategiesBuilder implements WebClientStrategies.Builder {
class DefaultExchangeStrategiesBuilder implements ExchangeStrategies.Builder {
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
DefaultWebClientStrategiesBuilder.class.getClassLoader()) &&
DefaultExchangeStrategiesBuilder.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
DefaultWebClientStrategiesBuilder.class.getClassLoader());
DefaultExchangeStrategiesBuilder.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder",
DefaultWebClientStrategiesBuilder.class.getClassLoader());
DefaultExchangeStrategiesBuilder.class.getClassLoader());
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
@@ -100,43 +100,43 @@ class DefaultWebClientStrategiesBuilder implements WebClientStrategies.Builder {
}
@Override
public WebClientStrategies.Builder messageReader(HttpMessageReader<?> messageReader) {
public ExchangeStrategies.Builder messageReader(HttpMessageReader<?> messageReader) {
Assert.notNull(messageReader, "'messageReader' must not be null");
this.messageReaders.add(messageReader);
return this;
}
@Override
public WebClientStrategies.Builder decoder(Decoder<?> decoder) {
public ExchangeStrategies.Builder decoder(Decoder<?> decoder) {
Assert.notNull(decoder, "'decoder' must not be null");
return messageReader(new DecoderHttpMessageReader<>(decoder));
}
@Override
public WebClientStrategies.Builder messageWriter(HttpMessageWriter<?> messageWriter) {
public ExchangeStrategies.Builder messageWriter(HttpMessageWriter<?> messageWriter) {
Assert.notNull(messageWriter, "'messageWriter' must not be null");
this.messageWriters.add(messageWriter);
return this;
}
@Override
public WebClientStrategies.Builder encoder(Encoder<?> encoder) {
public ExchangeStrategies.Builder encoder(Encoder<?> encoder) {
Assert.notNull(encoder, "'encoder' must not be null");
return messageWriter(new EncoderHttpMessageWriter<>(encoder));
}
@Override
public WebClientStrategies build() {
return new DefaultWebClientStrategies(this.messageReaders, this.messageWriters);
public ExchangeStrategies build() {
return new DefaultExchangeStrategies(this.messageReaders, this.messageWriters);
}
private static class DefaultWebClientStrategies implements WebClientStrategies {
private static class DefaultExchangeStrategies implements ExchangeStrategies {
private final List<HttpMessageReader<?>> messageReaders;
private final List<HttpMessageWriter<?>> messageWriters;
public DefaultWebClientStrategies(
public DefaultExchangeStrategies(
List<HttpMessageReader<?>> messageReaders,
List<HttpMessageWriter<?>> messageWriters) {
this.messageReaders = unmodifiableCopy(messageReaders);

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.net.URI;
@@ -38,26 +39,26 @@ import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebClientOperations}.
* Default implementation of {@link WebClient}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
class DefaultWebClientOperations implements WebClientOperations {
class DefaultWebClient implements WebClient {
private final WebClient webClient;
private final ExchangeFunction exchangeFunction;
private final UriBuilderFactory uriBuilderFactory;
DefaultWebClientOperations(WebClient webClient, UriBuilderFactory factory) {
this.webClient = webClient;
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory) {
this.exchangeFunction = exchangeFunction;
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
}
private WebClient getWebClient() {
return this.webClient;
private ExchangeFunction getExchangeFunction() {
return this.exchangeFunction;
}
private UriBuilderFactory getUriBuilderFactory() {
@@ -107,9 +108,9 @@ class DefaultWebClientOperations implements WebClientOperations {
@Override
public WebClientOperations filter(ExchangeFilterFunction filterFunction) {
WebClient filteredWebClient = this.webClient.filter(filterFunction);
return new DefaultWebClientOperations(filteredWebClient, this.uriBuilderFactory);
public WebClient filter(ExchangeFilterFunction filterFunction) {
ExchangeFunction filteredExchangeFunction = this.exchangeFunction.filter(filterFunction);
return new DefaultWebClient(filteredExchangeFunction, this.uriBuilderFactory);
}
@@ -219,19 +220,19 @@ class DefaultWebClientOperations implements WebClientOperations {
@Override
public Mono<ClientResponse> exchange() {
ClientRequest<Void> request = this.requestBuilder.headers(this.headers).build();
return getWebClient().exchange(request);
return getExchangeFunction().exchange(request);
}
@Override
public <T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
ClientRequest<T> request = this.requestBuilder.headers(this.headers).body(inserter);
return getWebClient().exchange(request);
return getExchangeFunction().exchange(request);
}
@Override
public <T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass) {
ClientRequest<S> request = this.requestBuilder.headers(this.headers).body(publisher, elementClass);
return getWebClient().exchange(request);
return getExchangeFunction().exchange(request);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -16,101 +16,37 @@
package org.springframework.web.reactive.function.client;
import java.util.logging.Level;
import reactor.core.publisher.Mono;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebClient.Builder}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 5.0
*/
class DefaultWebClientBuilder implements WebClient.Builder {
private ClientHttpConnector clientHttpConnector;
private final ExchangeFunction exchangeFunction;
private WebClientStrategies strategies = WebClientStrategies.withDefaults();
private ExchangeFilterFunction filter = new NoOpFilter();
private UriBuilderFactory uriBuilderFactory;
public DefaultWebClientBuilder(ClientHttpConnector clientHttpConnector) {
this.clientHttpConnector = clientHttpConnector;
public DefaultWebClientBuilder(ExchangeFunction exchangeFunction) {
Assert.notNull(exchangeFunction, "'exchangeFunction' must not be null");
this.exchangeFunction = exchangeFunction;
}
@Override
public WebClient.Builder strategies(WebClientStrategies strategies) {
Assert.notNull(strategies, "'strategies' must not be null");
this.strategies = strategies;
return this;
}
@Override
public WebClient.Builder filter(ExchangeFilterFunction filter) {
Assert.notNull(filter, "'filter' must not be null");
this.filter = filter.andThen(this.filter);
public WebClient.Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.uriBuilderFactory = uriBuilderFactory;
return this;
}
@Override
public WebClient build() {
return new DefaultWebClient(this.clientHttpConnector, this.strategies, this.filter);
}
private final static class DefaultWebClient implements WebClient {
private final ClientHttpConnector clientHttpConnector;
private final WebClientStrategies strategies;
private final ExchangeFilterFunction filter;
public DefaultWebClient(
ClientHttpConnector clientHttpConnector,
WebClientStrategies strategies,
ExchangeFilterFunction filter) {
this.clientHttpConnector = clientHttpConnector;
this.strategies = strategies;
this.filter = filter;
}
@Override
public Mono<ClientResponse> exchange(ClientRequest<?> request) {
Assert.notNull(request, "'request' must not be null");
return this.filter.filter(request, this::exchangeInternal);
}
private Mono<ClientResponse> exchangeInternal(ClientRequest<?> request) {
return this.clientHttpConnector
.connect(request.method(), request.url(),
clientHttpRequest -> request
.writeTo(clientHttpRequest, this.strategies))
.log("org.springframework.web.client.reactive", Level.FINE)
.map(clientHttpResponse -> new DefaultClientResponse(clientHttpResponse,
this.strategies));
}
@Override
public WebClient filter(ExchangeFilterFunction filter) {
Assert.notNull(filter, "'filter' must not be null");
ExchangeFilterFunction composedFilter = filter.andThen(this.filter);
return new DefaultWebClient(this.clientHttpConnector, this.strategies, composedFilter);
}
}
private class NoOpFilter implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest<?> request, ExchangeFunction next) {
return next.exchange(request);
}
return new DefaultWebClient(this.exchangeFunction, this.uriBuilderFactory);
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2002-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import org.springframework.util.Assert;
import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebClientOperations.Builder}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
class DefaultWebClientOperationsBuilder implements WebClientOperations.Builder {
private final WebClient webClient;
private UriBuilderFactory uriBuilderFactory;
public DefaultWebClientOperationsBuilder(WebClient webClient) {
Assert.notNull(webClient, "WebClient is required");
this.webClient = webClient;
}
@Override
public WebClientOperations.Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.uriBuilderFactory = uriBuilderFactory;
return this;
}
@Override
public WebClientOperations build() {
return new DefaultWebClientOperations(this.webClient, this.uriBuilderFactory);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -18,9 +18,21 @@ package org.springframework.web.reactive.function.client;
import reactor.core.publisher.Mono;
import org.springframework.util.Assert;
/**
* Represents a function that exchanges a {@linkplain ClientRequest request} for a (delayed)
* {@linkplain ClientResponse}.
* {@linkplain ClientResponse}. Can be used as an alternative to {@link WebClient}.
* <p>For example:
* <pre class="code">
* ExchangeFunction exchangeFunction = ExchangeFunctions.create(new ReactorClientHttpConnector());
* ClientRequest&lt;Void&gt; request = ClientRequest.method(HttpMethod.GET,
* "http://example.com/resource").build();
*
* Mono&lt;String&gt; result = exchangeFunction
* .exchange(request)
* .then(response -> response.bodyToMono(String.class));
* </pre>
*
* @author Arjen Poutsma
* @since 5.0
@@ -31,8 +43,21 @@ public interface ExchangeFunction {
/**
* Exchange the given request for a response mono.
* @param request the request to exchange
* @return the response, wrapped in a {@code Mono}
* @return the delayed response
*/
Mono<ClientResponse> exchange(ClientRequest<?> request);
/**
* Filters this exchange function with the given {@code ExchangeFilterFunction}, resulting in a
* filtered {@code ExchangeFunction}.
* @param filter the filter to apply to this exchange
* @return the filtered exchange
* @see ExchangeFilterFunction#apply(ExchangeFunction)
*/
default ExchangeFunction filter(ExchangeFilterFunction filter) {
Assert.notNull(filter, "'filter' must not be null");
return filter.apply(this);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2002-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.util.logging.Level;
import reactor.core.publisher.Mono;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
/**
* Exposes request-response exchange functionality, such as to
* {@linkplain #create(ClientHttpConnector) create} an {@code ExchangeFunction} given a
* {@code ClientHttpConnector}.
*
* @author Arjen Poutsma
* @since 5.0
*/
public abstract class ExchangeFunctions {
/**
* Create a new {@link ExchangeFunction} with the given connector. This method uses
* {@linkplain ExchangeStrategies#withDefaults() default strategies}.
* @param connector the connector to create connections
* @return the created function
*/
public static ExchangeFunction create(ClientHttpConnector connector) {
return create(connector, ExchangeStrategies.withDefaults());
}
/**
* Create a new {@link ExchangeFunction} with the given connector and strategies.
* @param connector the connector to create connections
* @param strategies the strategies to use
* @return the created function
*/
public static ExchangeFunction create(ClientHttpConnector connector,
ExchangeStrategies strategies) {
Assert.notNull(connector, "'connector' must not be null");
Assert.notNull(strategies, "'strategies' must not be null");
return new DefaultExchangeFunction(connector, strategies);
}
private static class DefaultExchangeFunction implements ExchangeFunction {
private final ClientHttpConnector connector;
private final ExchangeStrategies strategies;
public DefaultExchangeFunction(
ClientHttpConnector connector,
ExchangeStrategies strategies) {
this.connector = connector;
this.strategies = strategies;
}
@Override
public Mono<ClientResponse> exchange(ClientRequest<?> request) {
Assert.notNull(request, "'request' must not be null");
return this.connector
.connect(request.method(), request.url(),
clientHttpRequest -> request.writeTo(clientHttpRequest, this.strategies))
.log("org.springframework.web.reactive.function.client", Level.FINE)
.map(clientHttpResponse -> new DefaultClientResponse(clientHttpResponse,
this.strategies));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -27,17 +27,17 @@ import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
/**
* Defines the strategies to be used by the {@link WebClient}. An instance of
* Defines the strategies for invoking {@link ExchangeFunction}s. An instance of
* this class is immutable; instances are typically created through the mutable {@link Builder}:
* either through {@link #builder()} to set up default strategies, or {@link #empty()} to start from
* scratch. Alternatively, {@code WebClientStrategies} instances can be created through
* scratch. Alternatively, {@code ExchangeStrategies} instances can be created through
* {@link #of(Supplier, Supplier)}.
*
* @author Brian Clozel
* @author Arjen Poutsma
* @since 5.0
*/
public interface WebClientStrategies {
public interface ExchangeStrategies {
// Instance methods
@@ -59,38 +59,38 @@ public interface WebClientStrategies {
// Static methods
/**
* Return a new {@code WebClientStrategies} with default initialization.
* @return the new {@code WebClientStrategies}
* Return a new {@code ExchangeStrategies} with default initialization.
* @return the new {@code ExchangeStrategies}
*/
static WebClientStrategies withDefaults() {
static ExchangeStrategies withDefaults() {
return builder().build();
}
/**
* Return a new {@code WebClientStrategies} based on the given
* Return a new {@code ExchangeStrategies} based on the given
* {@linkplain ApplicationContext application context}.
* The returned supplier will search for all {@link HttpMessageReader}, and
* {@link HttpMessageWriter} instances in the given application context and return them for
* {@link #messageReaders()}, and {@link #messageWriters()} respectively.
* @param applicationContext the application context to base the strategies on
* @return the new {@code WebClientStrategies}
* @return the new {@code ExchangeStrategies}
*/
static WebClientStrategies of(ApplicationContext applicationContext) {
static ExchangeStrategies of(ApplicationContext applicationContext) {
return builder(applicationContext).build();
}
/**
* Return a new {@code WebClientStrategies} described by the given supplier functions.
* Return a new {@code ExchangeStrategies} described by the given supplier functions.
* All provided supplier function parameters can be {@code null} to indicate an empty
* stream is to be returned.
* @param messageReaders the supplier function for {@link HttpMessageReader} instances (can be {@code null})
* @param messageWriters the supplier function for {@link HttpMessageWriter} instances (can be {@code null})
* @return the new {@code WebClientStrategies}
* @return the new {@code ExchangeStrategies}
*/
static WebClientStrategies of(Supplier<Stream<HttpMessageReader<?>>> messageReaders,
static ExchangeStrategies of(Supplier<Stream<HttpMessageReader<?>>> messageReaders,
Supplier<Stream<HttpMessageWriter<?>>> messageWriters) {
return new WebClientStrategies() {
return new ExchangeStrategies() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return checkForNull(messageReaders);
@@ -109,11 +109,11 @@ public interface WebClientStrategies {
// Builder methods
/**
* Return a mutable builder for a {@code WebClientStrategies} with default initialization.
* Return a mutable builder for a {@code ExchangeStrategies} with default initialization.
* @return the builder
*/
static Builder builder() {
DefaultWebClientStrategiesBuilder builder = new DefaultWebClientStrategiesBuilder();
DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder();
builder.defaultConfiguration();
return builder;
}
@@ -128,22 +128,22 @@ public interface WebClientStrategies {
*/
static Builder builder(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
DefaultWebClientStrategiesBuilder builder = new DefaultWebClientStrategiesBuilder();
DefaultExchangeStrategiesBuilder builder = new DefaultExchangeStrategiesBuilder();
builder.applicationContext(applicationContext);
return builder;
}
/**
* Return a mutable, empty builder for a {@code WebClientStrategies}.
* Return a mutable, empty builder for a {@code ExchangeStrategies}.
* @return the builder
*/
static Builder empty() {
return new DefaultWebClientStrategiesBuilder();
return new DefaultExchangeStrategiesBuilder();
}
/**
* A mutable builder for a {@link WebClientStrategies}.
* A mutable builder for a {@link ExchangeStrategies}.
*/
interface Builder {
@@ -180,10 +180,10 @@ public interface WebClientStrategies {
Builder encoder(Encoder<?> encoder);
/**
* Builds the {@link WebClientStrategies}.
* Builds the {@link ExchangeStrategies}.
* @return the built strategies
*/
WebClientStrategies build();
ExchangeStrategies build();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -16,43 +16,97 @@
package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.util.Assert;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.UriBuilderFactory;
/**
* Reactive Web client supporting the HTTP/1.1 protocol. Main entry point is through the
* {@link #exchange(ClientRequest)} method.
* The main class for performing Web requests.
*
* <p>For example:
* <pre class="code">
* WebClient client = WebClient.create(new ReactorClientHttpConnector());
* ClientRequest&lt;Void&gt; request = ClientRequest.GET("http://example.com/resource").build();
*
* Mono&lt;String&gt; result = client
* .exchange(request)
* .then(response -> response.bodyToMono(String.class));
* // Create ExchangeFunction (application-wide)
*
* ClientHttpConnector connector = new ReactorClientHttpConnector();
* ExchangeFunction exchangeFunction = ExchangeFunctions.create(connector);
*
* // Create WebClient (per base URI)
*
* String baseUri = "http://abc.com";
* UriBuilderFactory factory = new DefaultUriBuilderFactory(baseUri);
* WebClient operations = WebClient.create(exchangeFunction, factory);
*
* // Perform requests...
*
* Mono<String> result = operations.get()
* .uri("/foo")
* .exchange()
* .then(response -> response.bodyToMono(String.class));
* </pre>
*
* @author Brian Clozel
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 5.0
*/
public interface WebClient extends ExchangeFunction {
public interface WebClient {
/**
* Exchange the given request for a response mono. Invoking this method performs the actual
* HTTP request/response exchange.
* @param request the request to exchange
* @return the response, wrapped in a {@code Mono}
* Prepare an HTTP GET request.
* @return a spec for specifying the target URL
*/
@Override
Mono<ClientResponse> exchange(ClientRequest<?> request);
UriSpec get();
/**
* Filters this client with the given {@code ExchangeFilterFunction}, resulting in a filtered
* {@code WebClient}.
* Prepare an HTTP HEAD request.
* @return a spec for specifying the target URL
*/
UriSpec head();
/**
* Prepare an HTTP POST request.
* @return a spec for specifying the target URL
*/
UriSpec post();
/**
* Prepare an HTTP PUT request.
* @return a spec for specifying the target URL
*/
UriSpec put();
/**
* Prepare an HTTP PATCH request.
* @return a spec for specifying the target URL
*/
UriSpec patch();
/**
* Prepare an HTTP DELETE request.
* @return a spec for specifying the target URL
*/
UriSpec delete();
/**
* Prepare an HTTP OPTIONS request.
* @return a spec for specifying the target URL
*/
UriSpec options();
/**
* Filter the client with the given {@code ExchangeFilterFunction}.
* @param filterFunction the filter to apply to this client
* @return the filtered client
* @see ExchangeFilterFunction#apply(ExchangeFunction)
@@ -60,52 +114,204 @@ public interface WebClient extends ExchangeFunction {
WebClient filter(ExchangeFilterFunction filterFunction);
// Static, factory methods
/**
* Create a new instance of {@code WebClient} with the given connector. This method uses
* {@linkplain WebClientStrategies#withDefaults() default strategies}.
* @param connector the connector to create connections
* @return the created client
* Create {@code WebClient} that uses the given {@link ClientHttpConnector}.
* @param connector the underlying connector to use
*/
static WebClient create(ClientHttpConnector connector) {
return builder(connector).build();
return create(ExchangeFunctions.create(connector));
}
/**
* Return a builder for a {@code WebClient}.
* @param connector the connector to create connections
* @return a web client builder
* Create {@code WebClient} that uses the given {@link ClientHttpConnector} and
* {@link ExchangeStrategies}.
* @param connector the underlying connector to use
* @param strategies the strategies to use
*/
static Builder builder(ClientHttpConnector connector) {
Assert.notNull(connector, "'connector' must not be null");
return new DefaultWebClientBuilder(connector);
static WebClient create(ClientHttpConnector connector, ExchangeStrategies strategies) {
return create(ExchangeFunctions.create(connector, strategies));
}
/**
* Create {@code WebClient} that wraps the given {@link ExchangeFunction}.
* @param exchangeFunction the underlying exchange function to use
*/
static WebClient create(ExchangeFunction exchangeFunction) {
return builder(exchangeFunction).build();
}
/**
* Create {@code WebClient} with a builder for additional
* configuration options.
* @param exchangeFunction the underlying exchange function to use
*/
static WebClient.Builder builder(ExchangeFunction exchangeFunction) {
return new DefaultWebClientBuilder(exchangeFunction);
}
/**
* A mutable builder for a {@link WebClient}.
*/
interface Builder {
/**
* Replaces the default strategies with the ones provided by the given
* {@code WebClientStrategies}.
* @param strategies the strategies to use
* @return this builder
* Configure a {@code UriBuilderFactory} for use with this client for
* example to define a common "base" URI.
* @param uriBuilderFactory the URI builder factory
*/
Builder strategies(WebClientStrategies strategies);
Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
/**
* Adds a filter function <strong>before</strong> the currently registered filters (if any).
* @param filter the filter to add
* @return this builder
*/
Builder filter(ExchangeFilterFunction filter);
/**
* Builds the {@code WebClient}.
* @return the built client
* Builder the {@link WebClient} instance.
*/
WebClient build();
}
/**
* Contract for specifying the URI for a request.
*/
interface UriSpec {
/**
* Specify the URI using an absolute, fully constructed {@link URI}.
*/
HeaderSpec uri(URI uri);
/**
* Specify the URI for the request using a URI template and URI variables.
* If a {@link UriBuilderFactory} was configured for the client (e.g.
* with a base URI) it will be used to expand the URI template.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(String uri, Object... uriVariables);
/**
* Build the URI for the request using the {@link UriBuilderFactory}
* configured for this client.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction);
}
/**
* Contract for specifying request headers leading up to the exchange.
*/
interface HeaderSpec {
/**
* Set the list of acceptable {@linkplain MediaType media types}, as
* specified by the {@code Accept} header.
* @param acceptableMediaTypes the acceptable media types
* @return this builder
*/
HeaderSpec accept(MediaType... acceptableMediaTypes);
/**
* Set the list of acceptable {@linkplain Charset charsets}, as specified
* by the {@code Accept-Charset} header.
* @param acceptableCharsets the acceptable charsets
* @return this builder
*/
HeaderSpec acceptCharset(Charset... acceptableCharsets);
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* @param contentLength the content length
* @return this builder
* @see HttpHeaders#setContentLength(long)
*/
HeaderSpec contentLength(long contentLength);
/**
* Set the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
* @param contentType the content type
* @return this builder
* @see HttpHeaders#setContentType(MediaType)
*/
HeaderSpec contentType(MediaType contentType);
/**
* Add a cookie with the given name and value.
* @param name the cookie name
* @param value the cookie value
* @return this builder
*/
HeaderSpec cookie(String name, String value);
/**
* Copy the given cookies into the entity's cookies map.
*
* @param cookies the existing cookies to copy from
* @return this builder
*/
HeaderSpec cookies(MultiValueMap<String, String> cookies);
/**
* Set the value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
* @param ifModifiedSince the new value of the header
* @return this builder
*/
HeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince);
/**
* Set the values of the {@code If-None-Match} header.
* @param ifNoneMatches the new value of the header
* @return this builder
*/
HeaderSpec ifNoneMatch(String... ifNoneMatches);
/**
* Add the given, single header value under the given name.
* @param headerName the header name
* @param headerValues the header value(s)
* @return this builder
*/
HeaderSpec header(String headerName, String... headerValues);
/**
* Copy the given headers into the entity's headers map.
* @param headers the existing headers to copy from
* @return this builder
*/
HeaderSpec headers(HttpHeaders headers);
/**
* Perform the request without a request body.
* @return a {@code Mono} with the response
*/
Mono<ClientResponse> exchange();
/**
* Set the body of the request to the given {@code BodyInserter} and
* perform the request.
* @param inserter the {@code BodyInserter} that writes to the request
* @param <T> the type contained in the body
* @return a {@code Mono} with the response
*/
<T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter);
/**
* Set the body of the request to the given {@code Publisher} and
* perform the request.
* @param publisher the {@code Publisher} to write to the request
* @param elementClass the class of elements contained in the publisher
* @param <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @return a {@code Mono} with the response
*/
<T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass);
}
}

View File

@@ -1,295 +0,0 @@
/*
* Copyright 2002-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.UriBuilderFactory;
/**
* The main class for performing requests through a WebClient.
*
* <pre class="code">
*
* // Create WebClient (application-wide)
*
* ClientHttpConnector connector = new ReactorClientHttpConnector();
* WebClient webClient = WebClient.create(connector);
*
* // Create WebClientOperations (per base URI)
*
* String baseUri = "http://abc.com";
* UriBuilderFactory factory = new DefaultUriBuilderFactory(baseUri);
* WebClientOperations operations = WebClientOperations.create(webClient, factory);
*
* // Perform requests...
*
* Mono<String> result = operations.get()
* .uri("/foo")
* .exchange()
* .then(response -> response.bodyToMono(String.class));
* </pre>
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface WebClientOperations {
/**
* Prepare an HTTP GET request.
* @return a spec for specifying the target URL
*/
UriSpec get();
/**
* Prepare an HTTP HEAD request.
* @return a spec for specifying the target URL
*/
UriSpec head();
/**
* Prepare an HTTP POST request.
* @return a spec for specifying the target URL
*/
UriSpec post();
/**
* Prepare an HTTP PUT request.
* @return a spec for specifying the target URL
*/
UriSpec put();
/**
* Prepare an HTTP PATCH request.
* @return a spec for specifying the target URL
*/
UriSpec patch();
/**
* Prepare an HTTP DELETE request.
* @return a spec for specifying the target URL
*/
UriSpec delete();
/**
* Prepare an HTTP OPTIONS request.
* @return a spec for specifying the target URL
*/
UriSpec options();
/**
* Filter the client with the given {@code ExchangeFilterFunction}.
* @param filterFunction the filter to apply to this client
* @return the filtered client
* @see ExchangeFilterFunction#apply(ExchangeFunction)
*/
WebClientOperations filter(ExchangeFilterFunction filterFunction);
// Static, factory methods
/**
* Create {@link WebClientOperations} that wraps the given {@link WebClient}.
* @param webClient the underlying client to use
*/
static WebClientOperations create(WebClient webClient) {
return builder(webClient).build();
}
/**
* Create {@link WebClientOperations} with a builder for additional
* configuration options.
* @param webClient the underlying client to use
*/
static WebClientOperations.Builder builder(WebClient webClient) {
return new DefaultWebClientOperationsBuilder(webClient);
}
/**
* A mutable builder for a {@link WebClientOperations}.
*/
interface Builder {
/**
* Configure a {@code UriBuilderFactory} for use with this client for
* example to define a common "base" URI.
* @param uriBuilderFactory the URI builder factory
*/
Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
/**
* Builder the {@link WebClient} instance.
*/
WebClientOperations build();
}
/**
* Contract for specifying the URI for a request.
*/
interface UriSpec {
/**
* Specify the URI using an absolute, fully constructed {@link URI}.
*/
HeaderSpec uri(URI uri);
/**
* Specify the URI for the request using a URI template and URI variables.
* If a {@link UriBuilderFactory} was configured for the client (e.g.
* with a base URI) it will be used to expand the URI template.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(String uri, Object... uriVariables);
/**
* Build the URI for the request using the {@link UriBuilderFactory}
* configured for this client.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction);
}
/**
* Contract for specifying request headers leading up to the exchange.
*/
interface HeaderSpec {
/**
* Set the list of acceptable {@linkplain MediaType media types}, as
* specified by the {@code Accept} header.
* @param acceptableMediaTypes the acceptable media types
* @return this builder
*/
HeaderSpec accept(MediaType... acceptableMediaTypes);
/**
* Set the list of acceptable {@linkplain Charset charsets}, as specified
* by the {@code Accept-Charset} header.
* @param acceptableCharsets the acceptable charsets
* @return this builder
*/
HeaderSpec acceptCharset(Charset... acceptableCharsets);
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* @param contentLength the content length
* @return this builder
* @see HttpHeaders#setContentLength(long)
*/
HeaderSpec contentLength(long contentLength);
/**
* Set the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
* @param contentType the content type
* @return this builder
* @see HttpHeaders#setContentType(MediaType)
*/
HeaderSpec contentType(MediaType contentType);
/**
* Add a cookie with the given name and value.
* @param name the cookie name
* @param value the cookie value
* @return this builder
*/
HeaderSpec cookie(String name, String value);
/**
* Copy the given cookies into the entity's cookies map.
*
* @param cookies the existing cookies to copy from
* @return this builder
*/
HeaderSpec cookies(MultiValueMap<String, String> cookies);
/**
* Set the value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
* @param ifModifiedSince the new value of the header
* @return this builder
*/
HeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince);
/**
* Set the values of the {@code If-None-Match} header.
* @param ifNoneMatches the new value of the header
* @return this builder
*/
HeaderSpec ifNoneMatch(String... ifNoneMatches);
/**
* Add the given, single header value under the given name.
* @param headerName the header name
* @param headerValues the header value(s)
* @return this builder
*/
HeaderSpec header(String headerName, String... headerValues);
/**
* Copy the given headers into the entity's headers map.
* @param headers the existing headers to copy from
* @return this builder
*/
HeaderSpec headers(HttpHeaders headers);
/**
* Perform the request without a request body.
* @return a {@code Mono} with the response
*/
Mono<ClientResponse> exchange();
/**
* Set the body of the request to the given {@code BodyInserter} and
* perform the request.
* @param inserter the {@code BodyInserter} that writes to the request
* @param <T> the type contained in the body
* @return a {@code Mono} with the response
*/
<T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter);
/**
* Set the body of the request to the given {@code Publisher} and
* perform the request.
* @param publisher the {@code Publisher} to write to the request
* @param elementClass the class of elements contained in the publisher
* @param <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @return a {@code Mono} with the response
*/
<T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass);
}
}

View File

@@ -36,19 +36,20 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.bootstrap.RxNettyHttpServer;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;
/**
* @author Sebastien Deleuze
*/
public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private WebClientOperations operations;
private WebClient webClient;
@Before
@@ -58,15 +59,15 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
super.setup();
WebClient client = WebClient.create(new ReactorClientHttpConnector());
ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port);
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
this.webClient = WebClient.builder(exchange).uriBuilderFactory(factory).build();
}
@Test
public void writeAndFlushWith() throws Exception {
Mono<String> result = this.operations.get()
Mono<String> result = this.webClient.get()
.uri("/write-and-flush")
.exchange()
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
@@ -81,7 +82,7 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
@Test // SPR-14991
public void writeAndAutoFlushOnComplete() {
Mono<String> result = this.operations.get()
Mono<String> result = this.webClient.get()
.uri("/write-and-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class))
@@ -95,7 +96,7 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
@Test // SPR-14992
public void writeAndAutoFlushBeforeComplete() {
Flux<String> result = this.operations.get()
Flux<String> result = this.webClient.get()
.uri("/write-and-never-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -83,7 +83,7 @@ public class DefaultClientRequestBuilderTests {
.build();
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
WebClientStrategies strategies = mock(WebClientStrategies.class);
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
result.writeTo(request, strategies).block();
@@ -110,7 +110,7 @@ public class DefaultClientRequestBuilderTests {
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
WebClientStrategies strategies = mock(WebClientStrategies.class);
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -55,7 +55,7 @@ public class DefaultClientResponseTests {
private ClientHttpResponse mockResponse;
private WebClientStrategies mockWebClientStrategies;
private ExchangeStrategies mockExchangeStrategies;
private DefaultClientResponse defaultClientResponse;
@@ -63,9 +63,9 @@ public class DefaultClientResponseTests {
@Before
public void createMocks() {
mockResponse = mock(ClientHttpResponse.class);
mockWebClientStrategies = mock(WebClientStrategies.class);
mockExchangeStrategies = mock(ExchangeStrategies.class);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockWebClientStrategies);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies);
}
@Test
@@ -110,7 +110,7 @@ public class DefaultClientResponseTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockWebClientStrategies.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.body(toMono(String.class));
assertEquals("foo", resultMono.block());
@@ -131,7 +131,7 @@ public class DefaultClientResponseTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockWebClientStrategies.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
assertEquals("foo", resultMono.block());
@@ -146,7 +146,7 @@ public class DefaultClientResponseTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockWebClientStrategies.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
@@ -170,7 +170,7 @@ public class DefaultClientResponseTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockWebClientStrategies.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);
Mono<List<String>> result = resultFlux.collectList();
@@ -186,7 +186,7 @@ public class DefaultClientResponseTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockWebClientStrategies.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);
StepVerifier.create(resultFlux)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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,11 +42,11 @@ import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class WebClientStrategiesTests {
public class ExchangeStrategiesTests {
@Test
public void empty() {
WebClientStrategies strategies = WebClientStrategies.empty().build();
ExchangeStrategies strategies = ExchangeStrategies.empty().build();
assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
}
@@ -56,7 +56,7 @@ public class WebClientStrategiesTests {
HttpMessageReader<?> messageReader = new DummyMessageReader();
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
WebClientStrategies strategies = WebClientStrategies.of(
ExchangeStrategies strategies = ExchangeStrategies.of(
() -> Stream.of(messageReader),
() -> Stream.of(messageWriter));
@@ -74,7 +74,7 @@ public class WebClientStrategiesTests {
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
WebClientStrategies strategies = WebClientStrategies.of(applicationContext);
ExchangeStrategies strategies = ExchangeStrategies.of(applicationContext);
assertTrue(strategies.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(strategies.messageWriters().get()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -43,7 +43,7 @@ import static org.junit.Assert.assertThat;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* Integration tests using a {@link WebClient} through {@link WebClientOperations}.
* Integration tests using a {@link ExchangeFunction} through {@link WebClient}.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
@@ -52,17 +52,17 @@ public class WebClientIntegrationTests {
private MockWebServer server;
private WebClientOperations operations;
private WebClient webClient;
@Before
public void setup() {
this.server = new MockWebServer();
WebClient webClient = WebClient.create(new ReactorClientHttpConnector());
ExchangeFunction exchangeFunction = ExchangeFunctions.create(new ReactorClientHttpConnector());
UriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(this.server.url("/").toString());
this.operations = WebClientOperations.builder(webClient)
this.webClient = WebClient.builder(exchangeFunction)
.uriBuilderFactory(uriBuilderFactory)
.build();
}
@@ -77,7 +77,7 @@ public class WebClientIntegrationTests {
public void headers() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
Mono<HttpHeaders> result = this.operations.get()
Mono<HttpHeaders> result = this.webClient.get()
.uri("/greeting?name=Spring")
.exchange()
.map(response -> response.headers().asHttpHeaders());
@@ -101,7 +101,7 @@ public class WebClientIntegrationTests {
public void plainText() throws Exception {
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
Mono<String> result = this.operations.get()
Mono<String> result = this.webClient.get()
.uri("/greeting?name=Spring")
.header("X-Test-Header", "testvalue")
.exchange()
@@ -125,7 +125,7 @@ public class WebClientIntegrationTests {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Mono<String> result = this.operations.get()
Mono<String> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.exchange()
@@ -147,7 +147,7 @@ public class WebClientIntegrationTests {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
Mono<Pojo> result = this.operations.get()
Mono<Pojo> result = this.webClient.get()
.uri("/pojo")
.accept(MediaType.APPLICATION_JSON)
.exchange()
@@ -169,7 +169,7 @@ public class WebClientIntegrationTests {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
Flux<Pojo> result = this.operations.get()
Flux<Pojo> result = this.webClient.get()
.uri("/pojos")
.accept(MediaType.APPLICATION_JSON)
.exchange()
@@ -193,7 +193,7 @@ public class WebClientIntegrationTests {
.setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
Mono<Pojo> result = this.operations.post()
Mono<Pojo> result = this.webClient.post()
.uri("/pojo/capitalize")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
@@ -219,7 +219,7 @@ public class WebClientIntegrationTests {
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "text/plain").setBody("test"));
Mono<String> result = this.operations.get()
Mono<String> result = this.webClient.get()
.uri("/test")
.cookie("testkey", "testvalue")
.exchange()
@@ -241,7 +241,7 @@ public class WebClientIntegrationTests {
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
Mono<ClientResponse> result = this.operations.get().uri("/greeting?name=Spring").exchange();
Mono<ClientResponse> result = this.webClient.get().uri("/greeting?name=Spring").exchange();
StepVerifier.create(result)
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.statusCode()))
@@ -258,7 +258,7 @@ public class WebClientIntegrationTests {
public void buildFilter() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
WebClientOperations filteredClient = this.operations.filter(
WebClient filteredClient = this.webClient.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
@@ -284,7 +284,7 @@ public class WebClientIntegrationTests {
public void filter() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
WebClientOperations filteredClient = this.operations.filter(
WebClient filteredClient = this.webClient.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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,8 +26,9 @@ import reactor.test.StepVerifier;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
@@ -44,15 +45,16 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
*/
public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests {
private WebClientOperations operations;
private WebClient webClient;
@Before
public void setup() throws Exception {
super.setup();
WebClient client = WebClient.create(new ReactorClientHttpConnector());
ExchangeFunction exchangeFunction =
ExchangeFunctions.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port);
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
this.webClient = WebClient.builder(exchangeFunction).uriBuilderFactory(factory).build();
}
@Override
@@ -65,7 +67,7 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
@Test
public void sseAsString() throws Exception {
Flux<String> result = this.operations.get()
Flux<String> result = this.webClient.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
@@ -79,7 +81,7 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
}
@Test
public void sseAsPerson() throws Exception {
Flux<Person> result = this.operations.get()
Flux<Person> result = this.webClient.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
@@ -94,7 +96,7 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
@Test
public void sseAsEvent() throws Exception {
Flux<ServerSentEvent<String>> result = this.operations.get()
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -35,8 +35,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebReactive;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
@@ -55,16 +56,16 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private AnnotationConfigApplicationContext wac;
private WebClientOperations operations;
private WebClient webClient;
@Override
@Before
public void setup() throws Exception {
super.setup();
WebClient client = WebClient.create(new ReactorClientHttpConnector());
ExchangeFunction exchangeFunction = ExchangeFunctions.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port + "/sse");
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
this.webClient = WebClient.builder(exchangeFunction).uriBuilderFactory(factory).build();
}
@@ -79,7 +80,7 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsString() throws Exception {
Flux<String> result = this.operations.get()
Flux<String> result = this.webClient.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
@@ -93,7 +94,7 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
}
@Test
public void sseAsPerson() throws Exception {
Flux<Person> result = this.operations.get()
Flux<Person> result = this.webClient.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
@@ -109,7 +110,7 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEvent() throws Exception {
ResolvableType type = forClassWithGenerics(ServerSentEvent.class, String.class);
Flux<ServerSentEvent<String>> result = this.operations.get()
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
@@ -136,7 +137,7 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEventWithoutAcceptHeader() throws Exception {
Flux<ServerSentEvent<String>> result = this.operations.get()
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()