스프링 어플리케이션에서 HTTP 요청할 때 사용하는 방법으로 RestTemplate과 WebClient가 있다. 스프링 5.0 이전까지는 클라이언트에서 HTTP 접근을 위해 사용한 것은 RestTemplate 이었다. 스프링 5.0 에서 WebClient가 나왔고 현재는 WebClient를 사용하기를 권고하고 있다. 이번 팀 프로젝트를 진행하면서 외부 api호출 시 WebClient을 사용해보았다. 그럼 RestTemplate과 WebClient는 어떤 특징이 있으며 왜 WebClient를 사용하길 권고하는지 알아보도록 하자.
스프링 3.0에서부터 지원하며 HTTP 통신에 유용하게 쓸 수 있는 템플릿이다. REST 서비스를 호출하도록 설계되어 HTTP 프로토콜의 메서드 (GET, POST, DELETE, PUT)에 맞게 여러 메서드를 제공한다. RestTemplate은 다음과 같은 특징이 있다
RestTemplate 특징
1) 클라이언트 애플리케이션 구동 시 쓰레드 풀을 만든다.
2) Request는 먼저 queue에 쌓이고 가용 쓰레드가 있으면 해당 쓰레드에 할당된다. (1요청 당 1쓰레드 할당)
3) 각 쓰레드는 블로킹 방식이기 때문에 완료 응답이 올 때까지 다른 요청에 할당될 수 없다.
4) 쓰레드가 다 찼다면 이후 요청은 queue에 대기하게 된다.
RestTemplate은 Multi-Thread와 Blocking방식을 사용한다.
HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient 를 추상화(HttpEntity의 json, xml 등)해서 제공해준다. 따라서 내부 통신(HTTP 커넥션)에 있어서는 Apache HttpComponents 를 사용한다.
출처: https://sjh836.tistory.com/141
RestTemplate을 생성할 때 어떤 HttpClient를 사용할 것인지 ClientHttpRequestFactory를 전달하여 지정할 수 있다. 기본 생성자의 경우 내부적으로 ClientHttpRequestFactory 의 구현체SimpleClientHttpRequestFactory를 사용하여 초기화한다.
RestTemplate을 사용하기 위해서는 restTemplate.메소드명()
을 사용하면 된다.
출처 : https://velog.io/@soosungp33/스프링-RestTemplate-정리요청-함
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);
}
}
WebCleint는 스프링 5.0에서 추가된 인터페이스다. Spring WebFlux는 HTTP request를 수행하는 client인 WebClient 를 포함하고 있으며 반응형으로 동작하도록 설계되었다. 스프링 5.0 이후부터는 RestTemplate
의 대안으로 WebClient를 사용할 것을 권장한다. 실제로는 spring-webflux 라이브러리에 속하지만 이 솔루션은 동기 및 비동기 작업을 모두 지원하므로 서블릿 스택에서 실행되는 애플리케이션에도 적용 가능하다.
WebClient는 다음과 같은 특징이 있다.
출처: 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를 생성하는 데는 2가지의 방법이 있다.
uriBuilderFactory
: base url을 커스텀한 UriBuilderFactorydefaultHeader
: 모든 요청에 사용할 헤더defaultCookie
: 모든 요청에 사용할 쿠키defaultRequest
: 모든 요청을 커스텀할 Consumerfilter
: 모든 요청에 사용할 클라이언트 필터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를 받아오는 방법에는 두 가지가 있다.
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를 자동으로 비우므로 주의해야 한다.결국 RestTemplate과 WebClient의 가장 큰 차이점은 Non-Blocking과 비동기화 가능 여부일 것이다. 결국 이러한 차이점이 스프링에서 RestTemplate을 사용하는 것 보다 WebClinet의 사용을 권장하는 이유라고 생각한다.
해당 글을 참고해 보면 차이점을 가장 쉽게 이해할 수 있을 것이다
Non-Blocking?
시스템을 호출한 직후에 프로그램으로 제어가 다시 돌아와서 시스템 호출의 종료를 기다리지 않고 다음 동작을 진행한다. 호출한 시스템의 동작을 기다리지 않고 동시에 다른 작업을 진행할 수 있어서 작업의 속도가 빨라진다는 장점이 있다.
출처 : https://alwayspr.tistory.com/44