Spring 어플리케이션에서 HTTP 요청을 할 땐 주로 RestTemplate 을 사용했었습니다. 하지만 Spring 5.0 버전부터는 RestTemplate 은 유지 모드로 변경되고 향후 deprecated 될 예정입니다.
RestTemplate 의 대안으로 Spring 에서는 WebClient 사용을 강력히 권고하고 있으며 다음과 같은 특징을 가지고 있습니다.
- Non-blocking I/O
- Reactive Streams back pressure
- High concurrency with fewer hardware resources
- Functional-style, fluent API that takes advantage of Java 8 lambdas
- Synchronous and asynchronous interactions
- Streaming up to or streaming down from a server
Reactive 환경과 MSA를 생각하고 있다면 WebClient 사용을 적극 권장해 드리며, 기본 설정 방법을 차근차근 알아보도록 하겠습니다.
Configuration
WebClient 를 사용하기 위한 가장 간단한 방법은 static factory 를 통해 WebClient 를 생성해서 사용할 수 있습니다.
WebClient.create();
WebClient.create(String baseUrl);
하지만 default 값이나 filter 또는 ConnectionTimeOut 같은 값을 지정하여 생성하기 위해서는 Builder 클래스를 통해 생성하는 것이 좋습니다.
Builer() 를 통하면
- 모든 호출에 대한 기본 Header / Cookie 값 설정
- filter 를 통한 Request/Response 처리
- Http 메시지 Reader/Writer 조작
- Http Client Library 설정
등이 가능합니다.
Spring 여러 Bean 에서 사용하기 위해 @Configuration 을 통해 WebClient 를 선언합니다.
@Configuration
@Slf4j
public class WebClientConfig {
@Bean
public WebClient webClient() {
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024*1024*50))
.build();
exchangeStrategies
.messageWriters().stream()
.filter(LoggingCodecSupport.class::isInstance)
.forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
return WebClient.builder()
.clientConnector(
new ReactorClientHttpConnector(
HttpClient
.create()
.secure(
ThrowingConsumer.unchecked(
sslContextSpec -> sslContextSpec.sslContext(
SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build()
)
)
)
.tcpConfiguration(
client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 120_000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(180))
.addHandlerLast(new WriteTimeoutHandler(180))
)
)
)
)
.exchangeStrategies(exchangeStrategies)
.filter(ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> {
log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.debug("{} : {}", name, value)));
return Mono.just(clientRequest);
}
))
.filter(ExchangeFilterFunction.ofResponseProcessor(
clientResponse -> {
clientResponse.headers().asHttpHeaders().forEach((name, values) -> values.forEach(value -> log.debug("{} : {}", name, value)));
return Mono.just(clientResponse);
}
))
.defaultHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.3")
.build();
}
}
MaxInMemorySize
Spring WebFlux 에서는 어플리케이션 메모리 문제를 피하기 위해 codec 처리를 위한 in-memory buffer 값이 256KB로 기본설정 되어 있습니다. 이 제약 때문에 256KB보다 큰 HTTP 메시지를 처리하려고 하면 DataBufferLimitException 에러가 발생하게 됩니다. 이 값을 늘려주기 위해서는 ExchageStrategies.builder() 를 통해 값을 늘려줘야 합니다.
ExchangeStrategies exchangeStrategies =
ExchangeStrategies
.builder()
.codecs(configurer -> configurer.defaultCodecs()
.maxInMemorySize(1024*1024*50))
.build();
Logging
Debug 레벨 일 때 form Data 와 Trace 레벨 일 때 header 정보는 민감한 정보를 포함하고 있기 때문에, 기본 WebClient 설정에서는 위 정보를 로그에서 확인할 수 가 없습니다. 개발 진행 시 Request/Response 정보를 상세히 확인하기 위해서는 ExchageStrateges 와 logging level 설정을 통해 로그 확인이 가능하도록 해 주는 것이 좋습니다.
exchangeStrategies
.messageWriters().stream()
.filter(LoggingCodecSupport.class::isInstance)
.forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
ExchangeStrategies 를 통해 setEnableLoggingRequestDetails(boolen enable) 을 true 로 설정해 주고 application.yaml 에 개발용 로깅 레벨은 DEBUG 로 설정해 줍니다.
logging:
level:
org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG
Client Filters
Request 또는 Response 데이터에 대해 조작을 하거나 추가 작업을 하기 위해서는 WebClient.builder().filter() 메소드를 이용해야 합니다. ExchangeFilterFunction.ofRequestProcessor() 와 ExchangeFilterFunction.ofResponseProcessor() 를 통해 clientRequest 와 clientResponse 를 변경하거나 출력할 수 있습니다.
Request / Response header를 출력하는 예제를 다음과 같이 설정 할 수 있습니다.
WebClient.builder()
.filter(ExchangeFilterFunction.ofRequestProcessor(
clientRequest -> {
log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> log.debug("{} : {}", name, value)));
return Mono.just(clientRequest);
}
))
.filter(ExchangeFilterFunction.ofResponseProcessor(
clientResponse -> {
clientResponse.headers()
.asHttpHeaders()
.forEach((name, values) ->
values.forEach(value -> log.debug("{} : {}", name, value)));
return Mono.just(clientResponse);
}
))
HttpClient TimeOut
HttpClient 를 변경하거나 ConnectionTimeOut 과 같은 설정값을 변경하려면 WebClient.builder().clientConnector() 를 통해 Reactor Netty의 HttpClient 를 직접 설정해 줘야 합니다.
WebClient
.builder()
.clientConnector(
new ReactorClientHttpConnector(
HttpClient
.create()
.secure(
ThrowingConsumer.unchecked(
sslContextSpec -> sslContextSpec.sslContext(
SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
)
)
)
.tcpConfiguration(
client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 120_000)
.doOnConnected(
conn -> conn.addHandlerLast(new ReadTimeoutHandler(180))
.addHandlerLast(new WriteTimeoutHandler(180))
)
)
)
)
new ReactorClientHttpConnector() 를 통해 옵션이 추가된 새로운 HttpClient 를 설정해 줍니다.
위 예제에서는 HTTPS 인증서를 검증하지 않고 바로 접속하는 설정과, TCP 연결 시 ConnectionTimeOut , ReadTimeOut , WriteTimeOut 을 적용하는 설정을 추가하였습니다.
다음 포스팅에서는 스프링 WebClient에서 제공하는 메소드의 사용법에 대해 포스팅 할 예정입니다.
'Spring' 카테고리의 다른 글
[Spring] Annotation과 Reflection을 이용한 확장성이 좋은 AOP 만들기 (0) | 2022.05.20 |
---|---|
[Spring] 비동기 프로그래밍 TaskRejectedException (0) | 2022.04.20 |