Spring Web Reactive 모듈의 일부로 생성 되었으며 이러한 시나리오에서 클래식 RestTemplate
을 대체한다.
WebClient 는 Spring WebFlux 라이브러리의 일부다. 따라서 선언적 구성 으로 반응 유형( Mono 및 Flux )이 있는 기능적이고 유창한 API를 사용하여 클라이언트 코드를 추가로 작성할 수 있다.
Non-Blocking I/O 기반의 비동기식(Asynchronous) API이다.
Spring 프로젝트가 RestTemplate에서 WebClient로 마이그레이션하고 있는 추세이고 추후 지원을 중단할지도 모른다
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
가장 간단한 방법은 static factory 를 통해 WebClient를 생성해서 사용할 수 있다.
WebClient client = WebClient.create();
client.get()
.uri("http://localhost:9900/webclient/test-create")
.retrieve()
.bodyToMono(String.class);
1개의 값을 리턴할 때는 bodyToMono, 복수의 값을 리턴할 때는 bodyToFlux를 사용한다.
사용자 정의가 가능한 DefaultWebClientBuilder 클래스를 사용하여 WebClient를 생성 및 설정 할수 있다.
WebClient.builder()
.baseUrl("http://localhost:5011")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter(...)
.defaultHeader(...)
Spring @Configuration를 설정하여 사용 할수 있도록 한다.
@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();
}
}
Request Data를 버퍼링하기 위한 메모리의 기본값은 256KB이다. 이 제약 때문에 256KB보다 큰 HTTP 메시지를 처리하려고 하면 DataBufferLimitException 에러가 발생하게 된다.
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer
이 값을 늘려주기 위해서는 ExchageStrategies.builder() 를 통해 값을 늘려줘야 한다.
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
ExchageStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024 * 50))
.build();
Request/Response 정보를 상세히 확인하기 위해서는 ExchageStrateges 와 logging level 설정을 통해 로그 확인이 가능하다.
exchangeStrategies
.messageWriters().stream()
.filter(LoggingCodecSupport.class::isInstance)
.forEach(writer -> ((LoggingCodecSupport)writer).setEnableLoggingRequestDetails(true));
logging:
level:
org.springframework.web.reactive.function.client.ExchangeFunctions: DEBUG
Request/Response 데이터에 대해 필터링 기능을 사용 하기 위해 WebClient.builder().filter() 메소드를 이용해야 한다. ExchangeFilterFunction.ofRequestProcessor() 와 ExchangeFilterFunction.ofResponseProcessor() 를 통해 clientRequest 와 clientResponse 를 변경하거나 출력할 수 있다.
WebClient.builder()
Configuration 설정이나 Bean으로 생성한 Web Client의 기존 설정값을 상속해서 사용할 수 있도록 기능을 제공하고 있다.
mutate() 를 통해 builder() 를 다시 생성하여 옵션을 설정하여 재사용 가능하게 해준다.
WebClient a = WebClient.builder()
.baseUrl("https://some.com")
.build();
WebClient b = a.mutate()
.defaultHeader("user-agent", "WebClient")
.build();
WebClient c = b.mutate()
.defaultHeader(HttpHeaders.AUTHORIZATION, token)
.build();
HTTP 호출 결과를 가져오는 두 가지 방법으로 retrieve() 와 exchange() 가 존재함. retrieve 를 이용하면 바로 ResponseBody를 처리 할 수 있고, exchange 를 이용하면 세세한 컨트롤이 가능하다. 하지만 Spring에서는 exchange 를 이용하게 되면 Response 대한 모든 처리를 직접 하면서 발생할 수 있는 memory leak 가능성 때문에 가급적 retrieve 를 사용하기를 권고하고 있다고 한다.
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
https://docs.spring.io/spring-framework/docs/5.3.0-SNAPSHOT/spring-framework-reference/web-reactive.html#webflux-client
https://madplay.github.io/post/difference-between-resttemplate-and-webclient
https://www.baeldung.com/spring-5-webclient
https://juneyr.dev/2019-02-12/resttemplate-vs-webclient
https://happycloud-lee.tistory.com/220
https://medium.com/@odysseymoon/spring-webclient-%EC%82%AC%EC%9A%A9%EB%B2%95-5f92d295edc0