webClient와 timeout
Timeout
( https://yangbongsoo.tistory.com/30 이 글을 꼭 읽어볼 것...)
R/W 타임아웃
Read Timeout
서버로부터 응답을 읽는 데 걸리는 시간 제한. 요청은 보냈는데, 서버가 응답을 안 주면 이 시간이 지나면 실패 처리됨.
Write Timeout
클라이언트가 서버에 요청을 보내는 데 걸리는 시간 제한. 데이터가 전송되는데 너무 오래 걸리면 실패 처리됨.
클라이언트가 POST /some-api 요청을 보냄
Connect Timeout 서버에 연결을 시도하는 시간 (TCP handshake)
Write Timeout: 요청 바디를 서버에 다 보내는 데 걸리는 시간
서버가 응답 준비함
Read Timeout: 서버 응답이 오기까지 기다리는 시간
WebClient는 내부적으로 Netty 기반의 TcpClient를 사용하므로 ReadTimeoutHandler, WriteTimeoutHandler로 설정한다.
비동기/논블로킹 특성상 타임아웃은 Reactor Netty를 통해 설정한다.
WebClientConfig.kt--
@Configuration
class WebClientConfig () {
@Bean
fun webClient(): WebClient {
var provider = ConnectionProvider.builder()
var webClient = WebClient.builder()
.clientConnector(
ReactorClientHttpConnector(
HttpClient.create(provider)
.option(ChannelOption.TIMEOUT_MILI, 15000) // TCP 커넥션 연결 시도 시간
.doOnConnected { connection ->
connection.addHandlerLast(
ReadTimeoutHandler(...) // 서버 응답 기다리는 시간
).addHandlerLast(
WriteTimeoutHandler(...) // 요청 보내는 시간
)
}
)
)
}
}
------------
webClient code
---A // 비동기 흐름 유지
webClient.post()
.uri(uri)
.bodyValue(body)
.retrieve()
.bodyToMono(ResponseDto::class.java)
.timeout(timeoutDuration)
.onErrorResume(TimeoutException::class.java) {
Mono.just(ResponseDto.fail("timeout"))
}
----B // Coroutine에서 비동기 Mono의 결과를 꺼내는 suspend 연산
val responseBodyMono = webClient.post()
.uri(uri)
.bodyValue(body)
.retrieve()
.bodyToMono(String::class.java)
val responseBody = responseBodyMono
.timeout(Duration.ofMillis(timeoutMillis)) // ⚠ 타임아웃 시도
.awaitFirst() // ⚠ 코루틴으로 Mono → 값 추출 (blocking 아님)
awaitFirst()의 의미
Mono<T>는 비동기 스트림이다. awaitFirst()는 이를 코루틴 안에서 처음 값을 꺼낼 때까지 기다렸다가 반환하는 함수이다.
----------------------
webClient.post().timeout() 에서 이 timeout이 Connection timeout인지 R/W timeout인지는 어떻게 알아?
WebClient.post()....timeout(Duration)에서 사용하는 .timeout() 연산자는 Reactor의 Mono.timeout 연산이며, 이는 Connection / Read / Write 중 어떤 것도 명시적으로 구분하지 않는다.
webClient.post()
.uri(...)
.bodyValue(...)
.retrieve()
.bodyToMono(String::class.java)
.timeout(Duration.ofSeconds(5)) // 이것은 "응답을 받을 때까지의 전체 시간 제한"이다 Connection timeout, Write timeout, Read timeout을 모두 아우르는 상위 레벨의 타임아웃이다
-------------------------
R/W 타임아웃을 제어하려면? Netty 설정(ReadTimeoutHandler, WriteTimeoutHandler)이 필요
.timeout()만으로 충분한가? 보통은 응답까지의 총 시간 제한을 보장하는 데 충분. 그러나 커넥션이 안 붙는 상황 등을 대비하려면 저수준 타임아웃도 병행 설정 필요
------------------------
( https://icthuman.tistory.com/entry/Spring-WebClient-%EC%82%AC%EC%9A%A9-3-Configuration-Timeout )
D. reactor timeout
- Reactive Stream은 Publisher, Subscriber, Subscription 을 통해서 비동기 / 넌블러킹 / back pressure 처리하는 개념이다.
- 우리가 다루는 Spring WebFlux는 reactive stream의 구현체로 reactor를 사용하고 있으며 Mono / Flux가 Publisher의 구현체이다.
- 따라서 Exception, Retry등을 처리할때도 기존 방식 대신 reactive stream의 기능을 활용해주는 것이 장점을 충분히 살릴 수 있는 방법이라고 생각한다.
- Spring WebFlux에서는 WebClient의 호출결과를 받았을때 결과 Body를 Mono로 감싸주어 데이터를 전달하는 형태가 되는데, 해당 시간동안 데이터를 전달하지 못하게 되면 timeout 이 발생하게 된다.