Spring WebClient

이주오·2022년 1월 27일
1

spring

목록 보기
3/4

개요

스프링 어플리케이션에서 HTTP 요청할 때 사용하는 방법으로 RestTemplate과 WebClient가 있다. 스프링 5.0 이전까지는 클라이언트에서 HTTP 접근을 위해 사용한 것은 RestTemplate 이었다. 스프링 5.0 에서 WebClient가 나왔고 현재는 WebClient를 사용하기를 권고하고 있다. 이번 팀 프로젝트를 진행하면서 외부 api호출 시 WebClient을 사용해보았다. 그럼 RestTemplate과 WebClient는 어떤 특징이 있으며 왜 WebClient를 사용하길 권고하는지 알아보도록 하자.


RestTemplate

스프링 3.0에서부터 지원하며 HTTP 통신에 유용하게 쓸 수 있는 템플릿이다. REST 서비스를 호출하도록 설계되어 HTTP 프로토콜의 메서드 (GET, POST, DELETE, PUT)에 맞게 여러 메서드를 제공한다. RestTemplate은 다음과 같은 특징이 있다

RestTemplate 특징

  • 통신을 단순화하고 RESTful 원칙을 지킨다
  • 멀티쓰레드 방식을 사용
  • Blocking 방식을 사용

1) 클라이언트 애플리케이션 구동 시 쓰레드 풀을 만든다.

2) Request는 먼저 queue에 쌓이고 가용 쓰레드가 있으면 해당 쓰레드에 할당된다. (1요청 당 1쓰레드 할당)

3) 각 쓰레드는 블로킹 방식이기 때문에 완료 응답이 올 때까지 다른 요청에 할당될 수 없다.

4) 쓰레드가 다 찼다면 이후 요청은 queue에 대기하게 된다.


동작원리

RestTemplate은 Multi-Thread와 Blocking방식을 사용한다.

HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient 를 추상화(HttpEntity의 json, xml 등)해서 제공해준다. 따라서 내부 통신(HTTP 커넥션)에 있어서는 Apache HttpComponents 를 사용한다.

  1. 어플리케이션이 RestTemplate를 생성하고, URI, HTTP메소드 등의 헤더를 담아 요청한다.
  2. RestTemplate 는 HttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.
  3. RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.
  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.
  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.
  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.
  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.
  8. 어플리케이션에 반환된다.

출처: https://sjh836.tistory.com/141


RestTemplate 사용

RestTemplate을 생성할 때 어떤 HttpClient를 사용할 것인지 ClientHttpRequestFactory를 전달하여 지정할 수 있다. 기본 생성자의 경우 내부적으로 ClientHttpRequestFactory 의 구현체SimpleClientHttpRequestFactory를 사용하여 초기화한다.

RestTemplate을 사용하기 위해서는 restTemplate.메소드명() 을 사용하면 된다.

출처 : https://velog.io/@soosungp33/스프링-RestTemplate-정리요청-함


Connection Pool

RestTemplate 객체를 생성할때 별도의 파리미터 없이 new RestTempalte()으로 생성한다면 Connection Pool을 활용하지 않는 객체이다. 이말은 즉, 요청때 마다 새로운 TCP Connection 을 연결한다는 의미이며 이 때 소켓이 close () 된 이후 소켓이 "TIME_WAIT" 상태가 되는데 만약 요청이 많아진다면 TIME_WAIT 상태의 소켓들을 재사용하지 못해서 요청에 대한 응답에 지연이 생길 수 있다.

이러한 응답 지연 상황을 대비하여 DB가 Connection Pool을 이용하듯이 RestTemplate도 Connection Pool을 이용할 수 있다. 그러기 위해선 RestTemplate 내부 구성을 설정해줘야한다.

단, 호출하는 API 서버가 Keep-Alive를 지원해야지 RestTemplate의 Connection Pool을 활용할 수 있다. 타겟 서버가 Keep-Alive를 지원하지 않는다면 미리 Connection Pool을 만들어 놓지 못하고 요청마다 새로운 Connection이 연결되어 매번 핸드쉐이크가 발생된다. 따라서 Connection Pool을 위한 RestTemplate의 내부 설정이 무의미하게 된다.

@Configuration
public class RestTemplateConfig {
    @Bean
    HttpClient httpClient() {
        return HttpClientBuilder.create()
            .setMaxConnTotal(100)    //최대 오픈되는 커넥션 수
            .setMaxConnPerRoute(5)   //IP, 포트 1쌍에 대해 수행할 커넥션 수
            .build();
    }
 
    @Bean
    HttpComponentsClientHttpRequestFactory factory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(5000);        //읽기시간초과, ms
        factory.setConnectTimeout(3000);     //연결시간초과, ms
        factory.setHttpClient(httpClient);
 
        return factory;
    }
 
    @Bean
    RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }
}

WebClient

WebCleint는 스프링 5.0에서 추가된 인터페이스다. Spring WebFlux는 HTTP request를 수행하는 client인 WebClient 를 포함하고 있으며 반응형으로 동작하도록 설계되었다. 스프링 5.0 이후부터는 RestTemplate 의 대안으로 WebClient를 사용할 것을 권장한다. 실제로는 spring-webflux  라이브러리에 속하지만 이 솔루션은 동기 및 비동기 작업을 모두 지원하므로 서블릿 스택에서 실행되는 애플리케이션에도 적용 가능하다.

WebClient는 다음과 같은 특징이 있다.

  • 싱글 스레드 방식을 사용
  • Non-Blocking 방식을 사용
  • JSON, XML을 쉽게 응답받는다.

출처: https://luminousmen.com/post/asynchronous-programming-blocking-and-non-blocking

Core 당 1개의 Thread를 이용한다.

각 요청은 Event Loop내에 Job으로 등록이 되어 Event Loop는 각 Job을 제공자에게 요청한 후, 결과를 기다리지 않고 다른 Job을 처리한다.

Event Loop는 제공자로부터 callback으로 응답이 오면, 그 결과를 요청자에게 제공한다.


의존성 설정

webflux 의존성을 추가해줘야 한다. Gradle 기준으로 아래와 같이 의존성을 추가해주면 된다.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

WebClient 생성

WebClient를 생성하는 데는 2가지의 방법이 있다.

  • static factory method (WebClient.create();)
  • 다른 옵션을 사용하기 위해 Builder를 활용한 클래스 생성 (WebClient.builder())
    • uriBuilderFactory : base url을 커스텀한 UriBuilderFactory
    • defaultHeader : 모든 요청에 사용할 헤더
    • defaultCookie : 모든 요청에 사용할 쿠키
    • defaultRequest : 모든 요청을 커스텀할 Consumer
    • filter : 모든 요청에 사용할 클라이언트 필터
    • exchangeStrategies : HTTP 메시지 reader & writer 커스텀
    • clientConnector : HTTP 클라이언트 라이브러리 세팅
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);

Webclient webClient = WebClient
	                    .builder()
                      .uriBuilderFactory(factory)
	                    .baseUrl("http://localhost:8080")
	                    .defaultCookie("쿠키","쿠키값")
	                    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
	                    .build();

Response 받아오기

response를 받아오는 방법에는 두 가지가 있다.

  • retrieve() → body를 받아 디코딩하는 간단한 메서드
  • exchange() → ClientResponse를 상태값 그리고 헤더와 함께 가져온다

exchange()를 통해 세세한 컨트롤이 가능하지만, Response 컨텐츠에 대한 모든 처리를 직접 하면서 메모리 누수 가능성 때문에 retrieve()를 권고하고 있다.

WebClient client = WebClient.create("https://example.org");

Mono<Person> result = client.get()
      .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
      .retrieve()
      .bodyToMono(Person.class);

bodyToMono 는 가져온 body를 Reactor의 Mono 객체로 바꿔준다. Mono 객체는 0-1개의 결과를 처리하는 객체이다. Flux는 0-N개의 결과를 처리하는 객체이다.

block() 을 사용하면 RestTemplate 처럼 동기식으로 사용할 수 있다.

4xx, 5xx의 응답 코드를 받으면 WebClientResponseException 또는 HTTP 상태에 해당하는 WebClientResponseException.BadRequest 등 과 같은 하위 exception을 던진다. onStatus 메서드로 상태별 exception을 커스텀도 가능하다.

Mono<Person> result = client.get()
      .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
      .retrieve()
      .onStatus(HttpStatus::is4xxClientError, response -> ...)
      .onStatus(HttpStatus::is5xxServerError, response -> ...)
      .bodyToMono(Person.class);
  • onStatus 를 사용하는 경우, response에 body가 있다면 onStatus 콜백에서 소비하지 않으면 리소스 반환을 위해 body를 자동으로 비우므로 주의해야 한다.

RestTempalte 과 WebClient의 차이

결국 RestTemplate과 WebClient의 가장 큰 차이점은 Non-Blocking과 비동기화 가능 여부일 것이다. 결국 이러한 차이점이 스프링에서 RestTemplate을 사용하는 것 보다 WebClinet의 사용을 권장하는 이유라고 생각한다.

해당 글을 참고해 보면 차이점을 가장 쉽게 이해할 수 있을 것이다

Non-Blocking?

시스템을 호출한 직후에 프로그램으로 제어가 다시 돌아와서 시스템 호출의 종료를 기다리지 않고 다음 동작을 진행한다. 호출한 시스템의 동작을 기다리지 않고 동시에 다른 작업을 진행할 수 있어서 작업의 속도가 빨라진다는 장점이 있다.


참고 - Spring WebFlux 성능

출처 : https://alwayspr.tistory.com/44


참고 출처

profile
동료들이 같이 일하고 싶어하는 백엔드 개발자가 되고자 합니다!

0개의 댓글