HTTP Client 비교 : RestTempalte, WebClient, RestClient

2ㅣ2ㅣ·2024년 10월 25일

Spring

목록 보기
5/7
post-thumbnail

SpringBoot를 사용해 HTTP API를 호출할 때 선택할 수 있는 방법을 알아보고, 어떤 상황에 어떤 클라이언트를 사용하는 것이 좋을지 정리해보겠다.

RestTemplate

HTTP 요청을 하기 위해 간단한 API를 제공하는 템플릿

  • 간단한 API 제공 : 서버 개발자들이 일일이 API를 만들지 않아도 RestTemplate이 간단한 API를 제공해줌
  • 동기 방식 : Java Servlet API를 활용하는 1 Thread 1 Reqest 모델을 따름
  • 별도 설정 필요

Build.gradle

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

Service

public BaseResponse<SongResponseDto> getChat(SongRequestDto songRequestDto) {
        String url = "http://localhost:8000/chat";
        try {
            ResponseEntity<SongResponseDto> response = restTemplate.postForEntity(url, songRequestDto, SongResponseDto.class);

            if (response.getStatusCode() == HttpStatus.OK) {
                return new BaseResponse<>(response.getBody());
            } else {
                throw new CustomException(BaseResponseStatus.INTERNAL_SERVER_ERROR);
            }
        } catch (Exception e) {
            throw new CustomException(BaseResponseStatus.INTERNAL_CLIENT_ERROR);
        }
    }

WebClient

기존 RestTemplate 기능을 제공하되, 비동기 논블로킹 방식을 사용하여 병렬 실행이 가능하게 하는 API CALL 방식

  • 비동기 방식 : 1 Thread N Reqest 모델을 따름
  • 리액티브 지원 : 논블로킹 방식의 데이터 처리를 지원하여, 높은 동시성을 필요로 하는 고성능 애플리케이션에 적합
  • 메서드 체이닝 방식 사용 : 높은 가독성 제공
  • 커스텀 오류 처리 가능

Build.gradle
starter-web 의존성 대신 web flux 의존성을 주입함. 동기 방식인 starter-web과 비동기 방식인 web-flux 동시에 주입하면 의존성 충돌이 발생하기 때문임

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

Service

public BaseResponse<SongResponseDto> getChat(SongRequestDto songRequestDto){
        SongResponseDto songResponseDto =  webClient.post()
                .uri("http://locathost:8000/chat")
                .bodyValue(songRequestDto)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError,
                        clientResponse -> Mono.error(new CustomException(BaseResponseStatus.INTERNAL_CLIENT_ERROR)))
                .onStatus(HttpStatusCode::is5xxServerError,
                        clientResponse -> Mono.error(new CustomException(BaseResponseStatus.INTERNAL_SERVER_ERROR)))
                .bodyToMono(SongResponseDto.class)
                .block();

        return new BaseResponse<>(songResponseDto);
    }
  • Status를 4xx과 5xx로 나누어 각 경우에 맞는 CustomException 처리 가능함

RestClient

RestTemplate + WebClient의 장점을 합친 API CALL 방식

  • 동기 방식 : 1 Thread 1 Reqest 모델을 따름
  • 재사용 : RestTemplate 설정을 재사용할 수 있는 기능 제공. 실제로 문법이 유사함.
  • 메서드 체이닝 방식 사용 : 높은 가독성 제공
  • 커스텀 오류 처리 가능

Build.gradle
web flux 관련 의존성을 제거함. RestTemplate을 재사용하여 동기식으로 처리하는게 RestTemplate의 의의이기 때문.

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

RestClientConfig
프록시 패턴을 사용하여 RestClient를 구현함.
프록시 패턴을 사용한 이유

  • RestTemplate을 재사용하기 위해
  • RestTemplate 설정을 캡슐화하여 서비스단에서 내부 로직 노출을 최소화하기 위해
  • 중앙 관리 및 직접 호출 방지 등을 위해
@Configuration
public class RestClientConfig {

    private static final int READ_TIMEOUT = 1500;
    private static final int CONNECT_TIMEOUT = 3000;

    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(createHttpClient());
        return new RestTemplate(factory);
    }

    // HttpClient 생성 메서드
    private CloseableHttpClient createHttpClient() {
        // 요청 설정에 대한 타임아웃 값 설정
        RequestConfig config = RequestConfig.custom()
                .setConnectionRequestTimeout(Timeout.ofMilliseconds(CONNECT_TIMEOUT))
                .setResponseTimeout(Timeout.ofMilliseconds(READ_TIMEOUT))
                .build();

        // HttpClient 생성 및 설정
        return HttpClients.custom()
                .setDefaultRequestConfig(config)
                .setConnectionManager(new PoolingHttpClientConnectionManager())
                .build();
    }

    @Bean
    public RestClient restClient(RestTemplate restTemplate) {
        return RestClient.create(restTemplate);
    }
}

Service

public BaseResponse<SongResponseDto> getChat(SongRequestDto songRequestDto) {
        String url = "http://localhost:8000/chat";

        ResponseEntity<SongResponseDto> response = restClient.post()
                .uri(url)
                .body(songRequestDto)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> {
                    throw new CustomException(BaseResponseStatus.INTERNAL_CLIENT_ERROR);
                })
                .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> {
                    throw new CustomException(BaseResponseStatus.INTERNAL_SERVER_ERROR);
                })
                .toEntity(SongResponseDto.class);

        return new BaseResponse<>(response.getBody());
}
  • 기존 RestTemplate 사용법과 유사
    • 이 코드에는 없지만 RestTemplate의 인프라를 공유하여 메시지 컨버터, 요청 팩토리, 인터셉터 등 모두 RestTemplate과 동일하게 커스텀하여 생성 가능
  • 기존 WebClient처럼 메서드 체이닝을 사용하고 커스텀 오류 처리 가능

✨ RestTemplate, WebClient, RestClient 비교 ✨

특징RestTemplateWebClientRestClient
도입 시기Spring 3.0 (구형)Spring 5.0 (리액티브 지원)Spring 6.1 (최신)
동기/비동기동기식(블로킹)동기/비동기 (비동기 및 논블로킹 지원)동기식(블로킹)
API 스타일템플릿 기반Fluent API (메서드 체이닝)Fluent API (메서드 체이닝)
리액티브 지원지원하지 않음리액티브 및 논블로킹 지원지원하지 않음
적합한 상황단순한 동기식 요청 처리복잡한 리액티브 또는 고성능 애플리케이션최신 동기식 HTTP 호출
설정 재사용재사용 불가완전한 커스터마이징 가능RestTemplate 설정을 재사용 가능
오류 처리기본적인 오류 처리고급 오류 처리 및 재시도 기능 지원고급 오류 처리 및 상태 코드 기반 처리

0개의 댓글