백엔드/스프링, 스프링부트, JPA, Spring Webflux

[Spring] WebClient 개념과 사용 방법

Kangjieun11 2023. 5. 30. 23:26
728x90

 

 

✅ webClient 

 

스프링 5부터 도입된 웹 클라이언트 라이브러리 (HTTP 요청을 수행)

비동기/논블로킹 방식으로 외부 API를 호출할 수 있다. 

 

😉 장점

비동기/논블로킹 방식을 지원하여, 높은 처리량과 확장성이 장점이다.

리액티브 프로그래밍을 할 수 있다. 데이터 스트림을 효과적으로 처리할 수 있다.

선언적 방식으로 API 호출을 정의하므로, 가독성이 좋다.

 

 

😵 단점

웹플럭스 학습곡선이 존재한다.

 

 

 

 


 

 

✅ 공식문서 & 참고 문서

 

공식문서에 웹클라이언트의 선언부터 사용방법이 자세하게 나와있다.

https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client

 

Redirecting...

 

docs.spring.io

 

 


 

✍️ WebClient 생성

 

✔️ create()메서드를 사용하면 간단하게 생성할수 있다.

WebClient.create()
WebClient.create(String baseUrl)

 

✔️ WebClient.builder() 

default 값 , filter, ConnectionTimeOut과 같은 것들을 지정해 생성하기 위해선 builder()를 사용한다.

 

  1. 모든 호출에 대해 Header, Cookie값 설정이 가능하다
  2. filter를 통해 Request, Response 처리가 가능하다
  3. Http 메세지 Reader Writer를 조작할 수 있다.
  4. 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()
  1. get()메서드를 호출하면 get요청이 생성된다
  2. uri()를 이요해 요청 URI를 설정한다
  3. retrieve()를 호출해서 응답을 가져온다.
  4. 응답 바디를 Mono로 변환한다. ResponseDto타입에 매핑된다.
  5. 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>()

 

 

  1. post()요청을 생성한다
  2. uri() : 요청 URI를 설정한다.
  3. 요청의 컨텐츠 타입을 설정한다. APPLICATION_JSON을 입력해, JSON타입의 데이터를 전송할 수 있다.
  4. bodyValue() : 요청의 바디에 넣어줄것을 설정한다.
  5. retrieve() : 응답을 가져오기 위한 호출
  6. 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