[Spring] WebClient 개념과 사용 방법
✅ webClient
스프링 5부터 도입된 웹 클라이언트 라이브러리 (HTTP 요청을 수행)
비동기/논블로킹 방식으로 외부 API를 호출할 수 있다.
😉 장점
비동기/논블로킹 방식을 지원하여, 높은 처리량과 확장성이 장점이다.
리액티브 프로그래밍을 할 수 있다. 데이터 스트림을 효과적으로 처리할 수 있다.
선언적 방식으로 API 호출을 정의하므로, 가독성이 좋다.
😵 단점
웹플럭스 학습곡선이 존재한다.
✅ 공식문서 & 참고 문서
공식문서에 웹클라이언트의 선언부터 사용방법이 자세하게 나와있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client
✍️ WebClient 생성
✔️ create()메서드를 사용하면 간단하게 생성할수 있다.
WebClient.create()
WebClient.create(String baseUrl)
✔️ WebClient.builder()
default 값 , filter, ConnectionTimeOut과 같은 것들을 지정해 생성하기 위해선 builder()를 사용한다.
- 모든 호출에 대해 Header, Cookie값 설정이 가능하다
- filter를 통해 Request, Response 처리가 가능하다
- Http 메세지 Reader Writer를 조작할 수 있다.
- Http Client Library 설정이 가능하다.
val webClient = WebClient.builder()
.codecs { configurer -> ... }
.build()
* 빈으로 사용하기 위해 @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();
}
}
* 빈으로 등록된 WebClient는 아래처럼 사용할 수 있다.
@Service
@RequiredArgsConstructor
@Slf4j
public class SomeService implements SomeInterface {
private final WebClient webClient;
public Mono<SomeData> getSomething() {
return webClient.mutate()
.build()
.get()
.uri("/resource")
.retrieve()
.bodyToMono(SomeData.class);
}
}
출처 : https://medium.com/@odysseymoon/spring-webclient-%EC%82%AC%EC%9A%A9%EB%B2%95-5f92d295edc0
✍️ retrieve & exchange
http 호출결과를 가져오는 2가지 방법이다.
- retrieve : 바로 ResponseBody 처리가 가능하다
- exchange : 세세한 컨트롤이 가능하다.
➡️ Spring에선 exchange를 이용하면 모든 처리를 직접하면서 발생가능한 memory leak가능성때문에
retrieve 사용이 권고된다.
✍️ WebClient를 이용해 GET 요청 보내기
get()메서드를 사용하면 WebClient를 이용해 GET요청을 보낼 수 있다.
➡️ 주어진 URI로부터 리소스를 가져오기 위해 사용된다.
👩💻 예제 코드
val response = client.get()
.uri("https://example.com/api/resource")
.retrieve()
.bodyToMono(ResponseDto::class.java)
.block()
- get()메서드를 호출하면 get요청이 생성된다
- uri()를 이요해 요청 URI를 설정한다
- retrieve()를 호출해서 응답을 가져온다.
- 응답 바디를 Mono로 변환한다. ResponseDto타입에 매핑된다.
- block() : Mono의 결과는 블로킹으로 동기적으로 받아온다.
✍️ WebClient를 이용해 POST요청 보내기
post()메서드를 사용하면 WebClient를 이용해 POST요청을 보낼 수 있다.
➡️ Body Contents를 전송할 수 있다
👩💻 예제 코드
val person: Person = ...
client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(person)
.retrieve()
.awaitBody<Unit>()
- post()요청을 생성한다
- uri() : 요청 URI를 설정한다.
- 요청의 컨텐츠 타입을 설정한다. APPLICATION_JSON을 입력해, JSON타입의 데이터를 전송할 수 있다.
- bodyValue() : 요청의 바디에 넣어줄것을 설정한다.
- retrieve() : 응답을 가져오기 위한 호출
- awaitBody<UNIT> : 비동기적으로 처리한다, UNIT = 처리된 결과가 필요하지 않다.
+ post()는 WebClient.RequestBodyUriSpec 객체를 반환한다.
* Start building an HTTP POST request.
* @return a spec for specifying the target URL
*/
RequestBodyUriSpec post();
✔️ RequestBodyUriSpec
Request의 header, body, URI를 지정하기 위한 계약(Contract)을 의미한다.
(WebClient를 이용해 HTTP 요청을 만들때 사용되는 구성요소이다)
- post() : post요청을 생성하는데 사용되고, RequestBodyUriSpec를 반환함
- uri() : 요청 URI를 설정
- header() : 요청 헤더 설정
- bodyValue() : 요청 body 설정
➡️ RequestBodyUriSpec는 RequestHeadersSpec를 확장하고 있다.
따라서 post()를 사용하면 요청의 header, body, URI를 한번에 구성이 가능하다
> 코드가 간결해지고 요청의 구성을 쉽게 할 수 있다.
아래 구현체를 보자. uri, headers, cookies를 한번에 처리할 수 있다.
오늘은 간단하게 WebClient사용방법을 알아보았다.
추가학습은 필요시 해야지.
References
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.RequestBodyUriSpec.html
- https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client
- https://howtodoinjava.com/spring-webflux/webclient-get-post-example/
- https://gngsn.tistory.com/154