Expose status handler at WebClient.Builder level
See gh-28533
This commit is contained in:
		
							parent
							
								
									4ed581cdd7
								
							
						
					
					
						commit
						a04e805d27
					
				| 
						 | 
				
			
			@ -21,6 +21,7 @@ import java.nio.charset.Charset;
 | 
			
		|||
import java.time.ZonedDateTime;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +30,7 @@ import java.util.function.Function;
 | 
			
		|||
import java.util.function.IntPredicate;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.reactivestreams.Publisher;
 | 
			
		||||
import reactor.core.publisher.Flux;
 | 
			
		||||
| 
						 | 
				
			
			@ -84,21 +86,35 @@ class DefaultWebClient implements WebClient {
 | 
			
		|||
	@Nullable
 | 
			
		||||
	private final Consumer<RequestHeadersSpec<?>> defaultRequest;
 | 
			
		||||
 | 
			
		||||
	private final List<DefaultResponseSpec.StatusHandler> defaultStatusHandlers;
 | 
			
		||||
 | 
			
		||||
	private final DefaultWebClientBuilder builder;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory uriBuilderFactory,
 | 
			
		||||
			@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> defaultCookies,
 | 
			
		||||
			@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest, DefaultWebClientBuilder builder) {
 | 
			
		||||
			@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
 | 
			
		||||
			@Nullable Map<Predicate<HttpStatusCode>, Function<ClientResponse, Mono<? extends Throwable>>> statusHandlerMap,
 | 
			
		||||
			DefaultWebClientBuilder builder) {
 | 
			
		||||
 | 
			
		||||
		this.exchangeFunction = exchangeFunction;
 | 
			
		||||
		this.uriBuilderFactory = uriBuilderFactory;
 | 
			
		||||
		this.defaultHeaders = defaultHeaders;
 | 
			
		||||
		this.defaultCookies = defaultCookies;
 | 
			
		||||
		this.defaultRequest = defaultRequest;
 | 
			
		||||
		this.defaultStatusHandlers = initStatusHandlers(statusHandlerMap);
 | 
			
		||||
		this.builder = builder;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static List<DefaultResponseSpec.StatusHandler> initStatusHandlers(
 | 
			
		||||
			@Nullable Map<Predicate<HttpStatusCode>, Function<ClientResponse, Mono<? extends Throwable>>> handlerMap) {
 | 
			
		||||
 | 
			
		||||
		return (CollectionUtils.isEmpty(handlerMap) ? Collections.emptyList() :
 | 
			
		||||
				handlerMap.entrySet().stream()
 | 
			
		||||
						.map(entry -> new DefaultResponseSpec.StatusHandler(entry.getKey(), entry.getValue()))
 | 
			
		||||
						.collect(Collectors.toList()));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public RequestHeadersUriSpec<?> get() {
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +381,8 @@ class DefaultWebClient implements WebClient {
 | 
			
		|||
 | 
			
		||||
		@Override
 | 
			
		||||
		public ResponseSpec retrieve() {
 | 
			
		||||
			return new DefaultResponseSpec(exchange(), this::createRequest);
 | 
			
		||||
			return new DefaultResponseSpec(
 | 
			
		||||
					exchange(), this::createRequest, DefaultWebClient.this.defaultStatusHandlers);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		private HttpRequest createRequest() {
 | 
			
		||||
| 
						 | 
				
			
			@ -502,11 +519,18 @@ class DefaultWebClient implements WebClient {
 | 
			
		|||
 | 
			
		||||
		private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
 | 
			
		||||
 | 
			
		||||
		private final int defaultStatusHandlerCount;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		DefaultResponseSpec(
 | 
			
		||||
				Mono<ClientResponse> responseMono, Supplier<HttpRequest> requestSupplier,
 | 
			
		||||
				List<StatusHandler> defaultStatusHandlers) {
 | 
			
		||||
 | 
			
		||||
		DefaultResponseSpec(Mono<ClientResponse> responseMono, Supplier<HttpRequest> requestSupplier) {
 | 
			
		||||
			this.responseMono = responseMono;
 | 
			
		||||
			this.requestSupplier = requestSupplier;
 | 
			
		||||
			this.statusHandlers.addAll(defaultStatusHandlers);
 | 
			
		||||
			this.statusHandlers.add(DEFAULT_STATUS_HANDLER);
 | 
			
		||||
			this.defaultStatusHandlerCount = this.statusHandlers.size();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -516,10 +540,9 @@ class DefaultWebClient implements WebClient {
 | 
			
		|||
 | 
			
		||||
			Assert.notNull(statusCodePredicate, "StatusCodePredicate must not be null");
 | 
			
		||||
			Assert.notNull(exceptionFunction, "Function must not be null");
 | 
			
		||||
			int index = this.statusHandlers.size() - 1;  // Default handler always last
 | 
			
		||||
			int index = this.statusHandlers.size() - this.defaultStatusHandlerCount;  // Default handlers always last
 | 
			
		||||
			this.statusHandlers.add(index, new StatusHandler(statusCodePredicate, exceptionFunction));
 | 
			
		||||
			return this;
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -22,8 +22,13 @@ import java.util.LinkedHashMap;
 | 
			
		|||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpStatusCode;
 | 
			
		||||
import org.springframework.http.client.reactive.ClientHttpConnector;
 | 
			
		||||
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector;
 | 
			
		||||
import org.springframework.http.client.reactive.JdkClientHttpConnector;
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +87,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
 | 
			
		|||
	@Nullable
 | 
			
		||||
	private Consumer<WebClient.RequestHeadersSpec<?>> defaultRequest;
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	private Map<Predicate<HttpStatusCode>, Function<ClientResponse, Mono<? extends Throwable>>> statusHandlers;
 | 
			
		||||
 | 
			
		||||
	@Nullable
 | 
			
		||||
	private List<ExchangeFilterFunction> filters;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,6 +128,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
 | 
			
		|||
		this.defaultCookies = (other.defaultCookies != null ?
 | 
			
		||||
				new LinkedMultiValueMap<>(other.defaultCookies) : null);
 | 
			
		||||
		this.defaultRequest = other.defaultRequest;
 | 
			
		||||
		this.statusHandlers = (other.statusHandlers != null ? new LinkedHashMap<>(other.statusHandlers) : null);
 | 
			
		||||
		this.filters = (other.filters != null ? new ArrayList<>(other.filters) : null);
 | 
			
		||||
 | 
			
		||||
		this.connector = other.connector;
 | 
			
		||||
| 
						 | 
				
			
			@ -193,6 +202,15 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
 | 
			
		|||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public WebClient.Builder defaultStatusHandler(Predicate<HttpStatusCode> statusPredicate,
 | 
			
		||||
			Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
 | 
			
		||||
 | 
			
		||||
		this.statusHandlers = (this.statusHandlers != null ? this.statusHandlers : new LinkedHashMap<>());
 | 
			
		||||
		this.statusHandlers.put(statusPredicate, exceptionFunction);
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public WebClient.Builder filter(ExchangeFilterFunction filter) {
 | 
			
		||||
		Assert.notNull(filter, "ExchangeFilterFunction must not be null");
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +300,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
 | 
			
		|||
		return new DefaultWebClient(filteredExchange, initUriBuilderFactory(),
 | 
			
		||||
				defaultHeaders,
 | 
			
		||||
				defaultCookies,
 | 
			
		||||
				this.defaultRequest, new DefaultWebClientBuilder(this));
 | 
			
		||||
				this.defaultRequest,
 | 
			
		||||
				this.statusHandlers,
 | 
			
		||||
				new DefaultWebClientBuilder(this));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private ClientHttpConnector initConnector() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -255,6 +255,20 @@ public interface WebClient {
 | 
			
		|||
		 */
 | 
			
		||||
		Builder defaultRequest(Consumer<RequestHeadersSpec<?>> defaultRequest);
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Register a default
 | 
			
		||||
		 * {@link ResponseSpec#onStatus(Predicate, Function) status handler} to
 | 
			
		||||
		 * apply to every response. Such default handlers are applied in the
 | 
			
		||||
		 * order in which they are registered, and after any others that are
 | 
			
		||||
		 * registered for a specific response.
 | 
			
		||||
		 * @param statusPredicate to match responses with
 | 
			
		||||
		 * @param exceptionFunction to map the response to an error signal
 | 
			
		||||
		 * @return this builder
 | 
			
		||||
		 * @since 6.0
 | 
			
		||||
		 */
 | 
			
		||||
		Builder defaultStatusHandler(Predicate<HttpStatusCode> statusPredicate,
 | 
			
		||||
				Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Add the given filter to the end of the filter chain.
 | 
			
		||||
		 * @param filter the filter to be added to the chain
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2021 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2022 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -423,6 +423,40 @@ public class DefaultWebClientTests {
 | 
			
		|||
		StepVerifier.create(result).expectErrorMessage("1").verify();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void onStatusHandlerRegisteredGlobally() {
 | 
			
		||||
 | 
			
		||||
		ClientResponse response = ClientResponse.create(HttpStatus.BAD_REQUEST).build();
 | 
			
		||||
		given(exchangeFunction.exchange(any())).willReturn(Mono.just(response));
 | 
			
		||||
 | 
			
		||||
		Mono<Void> result = this.builder
 | 
			
		||||
				.defaultStatusHandler(HttpStatusCode::is4xxClientError, resp -> Mono.error(new IllegalStateException("1")))
 | 
			
		||||
				.defaultStatusHandler(HttpStatusCode::is4xxClientError, resp -> Mono.error(new IllegalStateException("2")))
 | 
			
		||||
				.build().get()
 | 
			
		||||
				.uri("/path")
 | 
			
		||||
				.retrieve()
 | 
			
		||||
				.bodyToMono(Void.class);
 | 
			
		||||
 | 
			
		||||
		StepVerifier.create(result).expectErrorMessage("1").verify();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void onStatusHandlerRegisteredGloballyHaveLowerPrecedence() {
 | 
			
		||||
 | 
			
		||||
		ClientResponse response = ClientResponse.create(HttpStatus.BAD_REQUEST).build();
 | 
			
		||||
		given(exchangeFunction.exchange(any())).willReturn(Mono.just(response));
 | 
			
		||||
 | 
			
		||||
		Mono<Void> result = this.builder
 | 
			
		||||
				.defaultStatusHandler(HttpStatusCode::is4xxClientError, resp -> Mono.error(new IllegalStateException("1")))
 | 
			
		||||
				.build().get()
 | 
			
		||||
				.uri("/path")
 | 
			
		||||
				.retrieve()
 | 
			
		||||
				.onStatus(HttpStatusCode::is4xxClientError, resp -> Mono.error(new IllegalStateException("2")))
 | 
			
		||||
				.bodyToMono(Void.class);
 | 
			
		||||
 | 
			
		||||
		StepVerifier.create(result).expectErrorMessage("2").verify();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test // gh-23880
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	public void onStatusHandlersDefaultHandlerIsLast() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue