새로운 HTTP 클라이언트 RestClient

Ji, SeunghyeonJi, Seunghyeon
2 min read

Spring Framework 6.1 M2에서 새로운 동기 HTTP 클라이언트인 RestClient가 출시되었습니다! 그런데 그게 뭔데요? 원래 WebClient에서도 동기로 HTTP 쓸 수 있었잖아요?

Spring MVC에서 WebClient 하나 쓰겠다며 울며 겨자 먹기로 추가하던 spring-boot-starter-webflux를 지워버리셔도 된다는 말입니다.

따라서 이제 저희는 RestClient를 사용해서 RestTemplate의 기능들을 좀 더 가볍고 유연하게 사용할 수 있습니다.

💡RestClient 생성

RestClient 클래스의 static 메서드인 create()를 이용해서 생성할 수 있습니다.

RestClient restClient = RestClient.create();

아래과 같이 기존 RestTemplate의 구성으로 RestClient를 초기화할 수 있습니다.

RestClient restClient = RestClient.create(restTemplate);

또한 빌더 패턴을 사용해서 기본 URL, 헤더 설정 등등 추가 옵션들을 사용할 수도 있습니다.

RestClient restClient = RestClient.builder()
                .baseUrl("https://www.naver.com")
                .defaultHeaders(HttpHeaders::clearContentHeaders)
                .build();

💡GET & POST 요청

아래의 코드는 RestClient로 GET 요청을 하고 사이트 내용을 문자열로 검색합니다.

RestClient restClient = RestClient.create();

String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);
System.out.println(result);

HTTP의 컨텐츠 뿐만 아니라 상태 코드나 헤더를 받고 싶다면 ResponseEntity를 쓸 수 있습니다.

ResponseEntity<String> result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .toEntity(String.class);

System.out.println("Response status: " + result.getStatusCode());
System.out.println("Response headers: " + result.getHeaders());
System.out.println("Contents: " + result.getBody());

Jackson을 사용하여 JSON을 바로 객체로 변환할 수도 있습니다. 내부에서 RestTemplate과 똑같은 메시지 컨버터를 사용합니다.

int id = ...
Pet pet = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .retrieve()
  .body(Pet.class);

POST 요청 또한 간단하게 다음과 같이 쓸 수 있습니다.

Pet pet = ...
ResponseEntity<Void> response = restClient.post()
  .uri("https://petclinic.example.com/pets/new")
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();

💡에러 핸들링

기본적으로 RestClient는 4xx 또는 5xx 상태 코드를 수신할 때 RestClientException 하위 클래스를 던집니다. 이 때 다음과 같은 상태 핸들러를 사용하여 재정의할 수 있습니다.

String result = restClient.get()
  .uri("https://example.com/this-url-does-not-exist")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);

💡Exchange

RestClient는 기본 HTTP 요청 및 응답에 대한 액세스를 제공하므로 보다 고급 시나리오에 대한 exchange()를 제공합니다.

앞서 언급한 상태 핸들러는 exchange 기능이 이미 전체 응답에 대한 액세스를 제공하므로 필요한 오류 처리를 수행할 수 있으므로 exchange()를 사용할 때 적용되지 않습니다.

Pet result = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .exchange((request, response) -> {
    if (response.getStatusCode().is4xxClientError()) {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
    }
    else {
      Pet pet = convertResponse(response);
      return pet;
    }
  });

대체적으로 WebClient랑 사용법이 똑같다는 느낌이 듭니다. 근데 중요한 것은 저희가 쓸 데 없이 무거운 spring-boot-starter-webflux를 추가하지 않았다는 것이죠!

현재 진행 중인 개인 토이 프로젝트의 버전을 올려서 RestTemplate 대신 좀 더 현대적인 RestClient를 사용해야겠습니다.


🎯정리

  • RestTemplateTemplate과 같은 클래스를 통해 모든 HTTP의 기능을 노출하는 것이 부담을 줄 수 있었습니다. 그래서 저희는 WebClient를 썼었습니다.

  • WebClientspring-boot-starter-webflux 의존성을 추가해야 하는 부담이 있었습니다.

  • Spring 6.1 M2, Spring Boot 3.2.0-M1부터 RestClient를 사용할 수 있습니다. WebFlux 의존성을 추가하지 않고도 WebClient처럼 너무 많은 메서드를 오버라이딩 하지 않으면서 동기적으로 HTTP 요청을 할 수 있습니다.


🔖참고

0
Subscribe to my newsletter

Read articles from Ji, Seunghyeon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ji, Seunghyeon
Ji, Seunghyeon