This commit is contained in:
Rossen Stoyanchev 2017-05-02 17:59:22 -04:00
parent 44786ff840
commit 4db1eb1e4e
5 changed files with 37 additions and 36 deletions

View File

@ -35,19 +35,19 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
implements WebTestClient.MockServerSpec<B> {
private final ExchangeMutatorWebFilter exchangeMutatorFilter = new ExchangeMutatorWebFilter();
private final ExchangeMutatingWebFilter exchangeMutatingWebFilter = new ExchangeMutatingWebFilter();
private final List<WebFilter> filters = new ArrayList<>(4);
AbstractMockServerSpec() {
this.filters.add(this.exchangeMutatorFilter);
this.filters.add(this.exchangeMutatingWebFilter);
}
@Override
public <T extends B> T exchangeMutator(UnaryOperator<ServerWebExchange> mutator) {
this.exchangeMutatorFilter.register(mutator);
this.exchangeMutatingWebFilter.registerGlobalMutator(mutator);
return self();
}
@ -67,7 +67,7 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
public WebTestClient.Builder configureClient() {
WebHttpHandlerBuilder builder = initHttpHandlerBuilder();
filtersInReverse().forEach(builder::prependFilter);
return new DefaultWebTestClientBuilder(builder.build(), this.exchangeMutatorFilter);
return new DefaultWebTestClientBuilder(builder.build(), this.exchangeMutatingWebFilter);
}
/**

View File

@ -69,7 +69,7 @@ class DefaultWebTestClient implements WebTestClient {
private final WiretapConnector wiretapConnector;
private final ExchangeMutatorWebFilter exchangeMutatorWebFilter;
private final ExchangeMutatingWebFilter exchangeMutatingWebFilter;
private final Duration timeout;
@ -77,20 +77,20 @@ class DefaultWebTestClient implements WebTestClient {
DefaultWebTestClient(WebClient.Builder webClientBuilder, ClientHttpConnector connector,
ExchangeMutatorWebFilter webFilter, Duration timeout) {
ExchangeMutatingWebFilter exchangeMutatingWebFilter, Duration timeout) {
Assert.notNull(webClientBuilder, "WebClient.Builder is required");
this.wiretapConnector = new WiretapConnector(connector);
this.webClient = webClientBuilder.clientConnector(this.wiretapConnector).build();
this.exchangeMutatorWebFilter = webFilter;
this.exchangeMutatingWebFilter = exchangeMutatingWebFilter;
this.timeout = (timeout != null ? timeout : Duration.ofSeconds(5));
}
private DefaultWebTestClient(DefaultWebTestClient webTestClient, ExchangeFilterFunction filter) {
this.webClient = webTestClient.webClient.filter(filter);
this.wiretapConnector = webTestClient.wiretapConnector;
this.exchangeMutatorWebFilter = webTestClient.exchangeMutatorWebFilter;
this.exchangeMutatingWebFilter = webTestClient.exchangeMutatingWebFilter;
this.timeout = webTestClient.timeout;
}
@ -150,13 +150,13 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public WebTestClient exchangeMutator(UnaryOperator<ServerWebExchange> mutator) {
Assert.notNull(this.exchangeMutatorWebFilter,
Assert.notNull(this.exchangeMutatingWebFilter,
"This option is applicable only for tests without an actual running server");
return filter((request, next) -> {
String requestId = request.headers().getFirst(WiretapConnector.REQUEST_ID_HEADER_NAME);
Assert.notNull(requestId, "No request-id header");
this.exchangeMutatorWebFilter.register(requestId, mutator);
this.exchangeMutatingWebFilter.registerPerRequestMutator(requestId, mutator);
return next.exchange(request);
});
}

View File

@ -37,7 +37,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
private final ClientHttpConnector connector;
private final ExchangeMutatorWebFilter exchangeMutatorFilter;
private final ExchangeMutatingWebFilter exchangeMutatingWebFilter;
private Duration responseTimeout;
@ -48,12 +48,12 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
DefaultWebTestClientBuilder(ClientHttpConnector connector) {
this.connector = connector;
this.exchangeMutatorFilter = null;
this.exchangeMutatingWebFilter = null;
}
DefaultWebTestClientBuilder(HttpHandler httpHandler, ExchangeMutatorWebFilter exchangeMutatorFilter) {
DefaultWebTestClientBuilder(HttpHandler httpHandler, ExchangeMutatingWebFilter exchangeMutatingWebFilter) {
this.connector = new HttpHandlerConnector(httpHandler);
this.exchangeMutatorFilter = exchangeMutatorFilter;
this.exchangeMutatingWebFilter = exchangeMutatingWebFilter;
}
@ -96,7 +96,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
@Override
public WebTestClient build() {
return new DefaultWebTestClient(this.webClientBuilder, this.connector,
this.exchangeMutatorFilter, this.responseTimeout);
this.exchangeMutatingWebFilter, this.responseTimeout);
}
}

View File

@ -29,15 +29,18 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
/**
* WebFilter that applies global and request-specific transformation on
* WebFilter for applying global and per-request transformations to a
* {@link ServerWebExchange}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
class ExchangeMutatorWebFilter implements WebFilter {
class ExchangeMutatingWebFilter implements WebFilter {
private volatile Function<ServerWebExchange, ServerWebExchange> globalMutator;
private static final Function<ServerWebExchange, ServerWebExchange> NO_OP_MUTATOR = e -> e;
private volatile Function<ServerWebExchange, ServerWebExchange> globalMutator = NO_OP_MUTATOR;
private final Map<String, Function<ServerWebExchange, ServerWebExchange>> perRequestMutators =
new ConcurrentHashMap<>(4);
@ -47,9 +50,9 @@ class ExchangeMutatorWebFilter implements WebFilter {
* Register a global transformation function to apply to all requests.
* @param mutator the transformation function
*/
public void register(UnaryOperator<ServerWebExchange> mutator) {
public void registerGlobalMutator(UnaryOperator<ServerWebExchange> mutator) {
Assert.notNull(mutator, "'mutator' is required");
this.globalMutator = this.globalMutator != null ? this.globalMutator.andThen(mutator) : mutator;
this.globalMutator = this.globalMutator.andThen(mutator);
}
/**
@ -57,7 +60,7 @@ class ExchangeMutatorWebFilter implements WebFilter {
* @param requestId the "request-id" header value identifying the request
* @param mutator the transformation function
*/
public void register(String requestId, UnaryOperator<ServerWebExchange> mutator) {
public void registerPerRequestMutator(String requestId, UnaryOperator<ServerWebExchange> mutator) {
this.perRequestMutators.compute(requestId,
(s, value) -> value != null ? value.andThen(mutator) : mutator);
}
@ -65,18 +68,15 @@ class ExchangeMutatorWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (this.globalMutator != null) {
exchange = this.globalMutator.apply(exchange);
}
String requestId = WiretapConnector.getRequestId(exchange.getRequest().getHeaders());
Function<ServerWebExchange, ServerWebExchange> mutator = this.perRequestMutators.remove(requestId);
if (mutator != null) {
exchange = mutator.apply(exchange);
}
exchange = this.globalMutator.apply(exchange);
exchange = getMutatorFor(exchange).apply(exchange);
return chain.filter(exchange);
}
private Function<ServerWebExchange, ServerWebExchange> getMutatorFor(ServerWebExchange exchange) {
String id = WiretapConnector.getRequestId(exchange.getRequest().getHeaders());
Function<ServerWebExchange, ServerWebExchange> mutator = this.perRequestMutators.remove(id);
return mutator != null ? mutator : NO_OP_MUTATOR;
}
}

View File

@ -375,22 +375,23 @@ public interface WebClient {
* Exchange the request for a {@code ClientResponse} with full access
* to the response status and headers before extracting the body.
*
* <p>Use {@link Mono#then(Function)} or {@link Mono#flatMap(Function)}
* to compose further on the response:
* <p>Use {@link Mono#flatMap(Function)} or
* {@link Mono#flatMapMany(Function)} to compose further on the response:
*
* <pre>
* Mono&lt;Pojo&gt; mono = client.get().uri("/")
* .accept(MediaType.APPLICATION_JSON)
* .exchange()
* .then(response -> response.bodyToMono(Pojo.class));
* .flatMap(response -> response.bodyToMono(Pojo.class));
*
* Flux&lt;Pojo&gt; flux = client.get().uri("/")
* .accept(MediaType.APPLICATION_STREAM_JSON)
* .exchange()
* .then(response -> response.bodyToFlux(Pojo.class));
* .flatMapMany(response -> response.bodyToFlux(Pojo.class));
* </pre>
*
* @return a {@code Mono} with the response
* @see #retrieve()
*/
Mono<ClientResponse> exchange();