WebClient는 다양한 종류의 타임 아웃을 설정할 수 있다. 각 타임아웃은 다른 계층에서 작동하며 필요에 따라 세밀하게 제어할 수 있다.
HttpClient를 사용하여 설정하며 연결을 설정하는 데 걸리는 최대 시간을 정의한다.public class TimeoutWebClientConfiguration {
@Bean
public WebClient connectionTimeoutWebClient(WebClient.Builder clientBuilder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
return clientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8080")
.build();
}
}
HttpClient의 doOnConnected 콜백을 통해 ReadTimeoutHandler를 추가하여 설정한다. 응답 데이터의 읽기 작업이 완료될 때까지 기다리는 최대 시간을public class TimeoutWebClientConfiguration {
@Bean
public WebClient readTimeoutWebClient(WebClient.Builder clientBuilder) {
HttpClient httpClient = HttpClient.create()
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
);
return clientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
HttpClient의 doOnConnected콜백을 통해 WriteTimeoutHandler를 추가하여 설정한다. 요청 데이터를 쓰는 데 걸리는 최대 시간을 정의한다.public class TimeoutWebClientConfiguration {
@Bean
public WebClient writeTimeoutWebClient(WebClient.Builder clientBuilder) {
HttpClient httpClient = HttpClient.create()
.doOnConnected(connection ->
connection.addHandlerLast(new WriteTimeoutHandler(10, TimeUnit.SECONDS))
);
return clientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
HttpClient에 responseTimeout()메소드를 사용하여 설정한다. 연결이 설정된 후 응답이 시작되기까지 기다리는 최대 시간을 정의한다.public class TimeoutWebClientConfiguration {
@Bean
public WebClient responseTimeoutWebClient(WebClient.Builder clientBuilder) {
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(10)) // 10초
return clientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
Mono또는 Flux체인에 .timeout()연산자를 사용하여 설정한다. 이는 전체 리액티브 스트림 작업(연결, 요정 전송, 응답 수신 등 모든 과정)에 대한 높은 수준의
@RestController
@RequiredArgsConstructor
public class TimeoutController {
private final TimeoutService timeoutService;
@GetMapping("/example-endpoint")
public Mono<String> callApi() {
return timeoutService.callExampleEndpointWithTimeout();
}
}
@Service
public class TimeoutService {
private final WebClient webClient;
public TimeoutService(WebClient connectionTimeoutWebClient) {
this.webClient = connectionTimeoutWebClient;
}
public Mono<String> callExampleEndpointWithTimeout() {
return webClient.get()
.uri("/example-endpoint") // 실제로 호출할 API 경로
.retrieve()
.bodyToMono(String.class)
// 여기에 개별 요청에 대한 타임아웃 설정
.timeout(Duration.ofSeconds(5), Mono.just("Fallback 실패")) // 5초 안에 응답이 없으면 폴백
.doOnSuccess(success -> System.out.println("성공: " + success))
.doOnError(error -> {
// 폴백 메시지는 이미 처리되었으므로, 다른 예외에 대한 로깅
if (!error.getMessage().equals("Fallback 실패")) {
System.err.println("실패: " + error.getMessage());
}
});
// .subscribe()는 이 메서드를 호출하는 곳(컨트롤러 등)에서 필요에 따라 호출하거나,
// 리액티브 스트림을 계속 연결하여 처리하는 것이 일반적이다.
}
}
WebClient는 리액티브 스트림에서 에러가 발생했을 때 onError시그널을 발생시킨다. 이를 처리하는 다양한 방법이 있다.
특정 HTTP 상태 코드에 따라 커스텀 예외를 발생시키거나 다른 처리를 할 수 있다.
@Service
public class WebClientErrorHandlingService {
private final WebClient webClient;
public WebClientErrorHandlingService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> onStatusHandling() {
return webClient.get()
.uri("/example-endpoint")
.retrieve()
.onStatus(
HttpStatusCode::is4xxClientError,
clientResponse ->
Mono.error(new RuntimeException("Client Error: " + clientResponse.statusCode().value()))
)
.onStatus(
HttpStatusCode::is5xxServerError,
clientResponse ->
Mono.error(new RuntimeException("Server Error: " + clientResponse.statusCode().value()))
)
.bodyToMono(String.class);
}
}
에러가 발생했을 때 스트림을 종료하지 않고 미리 정의된 대체값을 반환한다.
@Service
public class WebClientErrorHandlingService {
private final WebClient webClient;
public WebClientErrorHandlingService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> onErrorReturnHandling() {
return webClient.get()
.uri("/example-endpoint")
.retrieve()
.bodyToMono(String.class)
.onErrorReturn("Default Value"); // 에러 발생 시 "Default Value" 반환
}
}
에러가 발생했을 때 다른 Publisher로 전환하여 폴백 로직을 수행할 수 있다.
@Service
public class WebClientErrorHandlingService {
private final WebClient webClient;
public WebClientErrorHandlingService(WebClient webClient) {
this.webClient = webClient;
}
public Publisher<String> onErrorResumeHandling() {
return webClient.get()
.uri("/error-endpoint")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> {
System.err.println("Error occurred: " + e.getMessage() + ". Falling back...");
return Mono.just("Fallback Data from Error"); // 에러 발생 시 다른 Mono 반환
});
}
}
발생한 예외를 다른 예외로 변환하여 상위 계층으로 전달한다.
@Service
public class WebClientErrorHandlingService {
private final WebClient webClient;
public WebClientErrorHandlingService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> onErrorMapHandling() {
return webClient.get()
.uri("/error-endpoint")
.retrieve()
.bodyToMono(String.class)
.onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("Read Timeout Occurred!"));
}
}
일시적인 네트워크 문제 등으로 인해 실패한 요청을 자동으로 재시도하도록 설정할 수 있다.
retry: 지정된 횟수만큼 에러 발생 시 재시도한다.retryWhen:더 복잡한 재시도 로직을 정의할 수 있다. 예를 들어 특정 조건에서만 재시도하거나 지수 백오프와 같은 지연 전략을 사용할 수 있다.
@Service
public class WebClientErrorHandlingService {
private final WebClient webClient;
public WebClientErrorHandlingService(WebClient webClient) {
this.webClient = webClient;
}
public Mono<String> retryHandling() {
return webClient.get()
.uri("/unstable-endpoint")
.retrieve()
.bodyToMono(String.class)
.retry(3); // 최대 3번 재시도
}
public Mono<String> retryWhenHandling() {
return webClient.get()
.uri("/unstable-endpoint")
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2)) // 3번 재시도, 2초부터 지수적으로 증가하는 지연
.filter(ex -> ex instanceof WebClientRequestException || ex instanceof HttpStatusCodeException)
.doBeforeRetry(retrySignal -> System.out.println("Retrying... " + retrySignal.totalRetriesInARow() + " time")));
}
}
WebClient.Builder를 통해 HttpClient를 구성하여 WebClient인스턴스를 생성할 때 타임아웃을 설정하면 해당 인스턴스를 사용하는 모든 요청에 동일한 타임아웃이 적용된다.Mono또는 Flux체인데 .timeout()연산자를 사용하는 것은 해당 특정 요청에만 적용되는 타임아웃이다..timeout()을 사용하여 재정의한다.