Spring Framework를 사용할 때 가장 많이 쓰게 되는 추상화된 HTTP Client 는
•
RestTemplate
•
WebClient
•
RestClient
이렇게 세 가지이다. 이 중 WebClient 는 webflux 의존성을 추가로 설정해야 한다는 단점이 있지만,
implementation("org.springframework.boot:spring-boot-starter-webflux")
Kotlin
복사
non-blocking 구현과 blocking 구현이 선택적으로 가능해 개인적으로 제일 선호하는 편이다.
모든 (TCP) Connection이 그렇듯 외부 커넥션을 맺을 때는 기본적으로 다음과 같은 구성요소가 있다.
•
Connection을 관리할 Pool
•
모든 Connection이 사용중일 때 요청을 대기시킬 Blocking Queue
또한 Connection을 설정할 때는 항상 timeout에 대한 고민도 할 수 밖에 없다. 보통은 다음과 같은 구성 요소이다.
•
Connect Timeout : 커넥션 자체가 맺어질 때의 타임아웃
•
Response Timeout : 요청을 보내고 응답을 받기까지의 전 과정에 대한 타임아웃
•
Read timeout : 청크 단위로 데이터를 읽다가 발생하는 타임아웃
•
Write timeout : 청크 단위로 데이터를 쓰다가 발생하는 타임아웃
이런 내용을 모두 고려한 내가 자주 사용하는 설정은 다음과 같다.
object ClientFactory {
fun createWebClient(url: String, name: String): WebClient {
val client = HttpClient
.create(
ConnectionProvider.builder("$name-http-connection-pool")
.maxConnections(20)
.pendingAcquireTimeout(Duration.ofSeconds(3))
.pendingAcquireMaxCount(200)
.maxIdleTime(Duration.ofSeconds(30))
.maxLifeTime(Duration.ofSeconds(55))
.build()
)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.responseTimeout(Duration.ofSeconds(5))
.doOnConnected { connection ->
connection
.addHandlerLast(ReadTimeoutHandler(5))
.addHandlerLast(WriteTimeoutHandler(5))
}
return WebClient.builder()
.baseUrl(url)
.filter { req, next ->
val map = MDC.getCopyOfContextMap()
next.exchange(req)
.doOnNext {
MDC.setContextMap(map)
}
}
.clientConnector(ReactorClientHttpConnector(client))
.build()
}
}
Kotlin
복사
하나씩 설정을 살펴보자
•
maxConnections : Pool에 들어갈 커넥션 최대 수
•
pendingAcquireTimeout : 요청이 밀려, 요청을 보내지 못한 경우 Blocking Queue에서 대기할 수 있는 최대 시간
•
pendingAcquireMaxCount : 최대 몇 개의 요청이 Blocking Queue에서 대기할 수 있는가?
•
maxIdleTime : 한 번 맺어진 Connection이 사용되지 않고 살아 있을 수 있는 최대 유휴 시간.
•
maxLifeTime : 한 번 맺어진 Connection을 언제까지 재활용할 것인지 최대 사용 시간.
•
CONNECT_TIMEOUT_MILLIS / responseTimeout / ReadTimeoutHandler / WriteTimeoutHandler : 위에서 언급한 timeout
val map = MDC.getCopyOfContextMap()
next.exchange(req)
.doOnNext {
MDC.setContextMap(map)
}
Kotlin
복사
이 부분은 코루틴을 사용해 스레드가 바뀌더라도 추적 가능하게 MDC를 복사 해주는 코드이다. 이 부분이 없으면, API 요청 이후 다른 스레드에서 코드가 실행될 때 추적이 끊긴다.
각 시간을 한 번에 설정할 수 있게 DTO로 분리하면 아래와 같은 느낌이 된다. (단위는 seconds로 통일했다)
data class ClientConfig(
val url: String,
val name: String,
val maxConnections: Int = 20,
val pendingAcquireTimeoutInSeconds: Long = 3,
val pendingAcquireMaxCount: Int = 1_000,
val maxIdleTimeInSeconds: Long = 30L,
val maxLifeTimeInSeconds: Long = 55L,
val connectTimeoutInSeconds: Int = 3,
val responseTimeoutInSeconds: Long = 5L,
val readTimeoutInSeconds: Int = 5,
val writeTimeoutInSeconds: Int = 5,
)
Kotlin
복사