Book/스프링부트 핵심가이드

스프링부트를 이용한 서버 간의 통신(of. WebClient)

블로그 주인장 2023. 12. 2.

서버 간의 통신

다른 서버가 API를 호출해서 사용할 수 있게 구성되어 웹 요청을 보내고 응답 받을 수 있게 도와주는 "WebClient"에 대해 알아보겠습니다.


WebClient


일반적으로 실제 운영환경에 적용되는 애플리케이션은 정식 버전으로 출시된 스프링 부트의 버전보다 낮은 경우가 많다.

그렇기 때문에 "RestTemplate" 을 많이 사용하고 있다.

하지만 최신 버전에서는 "RestTemplate" 이 지원 중단되어 WebClient를 사용할 것을 권고하고 있다.

 

Spring WebFlux

  • HTTP 요청을 수행하는 클라이언트로 WebClient를 제공한다.
  • WebClient는 리액터(Reactor) 기반으로 동작하는 API 입니다.
  • 리액터 기반으로 스레드와 동시성 문제를 벗어나 비동기 형식으로 사용할 수 있다.

 

WebClient의 특징

  • 논블로킹(Non-Blocking) I/O를 지원한다.
  • 리액티브 스트림(Reactive Stream)의 백 프레셔(Back Pressure)를 지원한다.
  • 적은 하드웨어 리소스로 동시성을 지원한다.
  • 함수형 API를 지원한다.
  • 동기 / 비동기 상호작용을 지원한다.
  • 스트리밍을 지원한다.

 

WebClient 구성(Maven)

  • WebFlux는 클라이언트와 서버 간 리액티브 애플리케이션 개발을 지원하기 위해 스프링 프레임워크 5에서 새롭게 추가된 모듈이다.
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>

 

WebClient GET 요청 예제

@Service
public class WebClientService {

    public String getName() {
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();

        return webClient.get()
                .uri("/api/v1/crud-api")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }

    public String getNameWithPathVariable() {
        WebClient webClient = WebClient.create("http://localhost:9090");

        ResponseEntity<String> responseEntity = webClient.get()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/{name}")
                        .build("test"))
                .retrieve().toEntity(String.class).block();

        return responseEntity.getBody();
    }
    
    public String getNameWithPathParameter() {
        WebClient webClient = WebClient.create("http://localhost:9090");
        
        return webClient.get().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "test")
                .build())
                .exchangeToMono(clientResponse -> {
                    if (clientResponse.statusCode().equals(HttpStatus.OK)) {
                        return clientResponse.bodyToMono(String.class);
                    } else {
                        return clientResponse.createException().flatMap(Mono::error);
                    }
                })
                .block();
    }
}
  • getName( ) 메서드는 builder( ) 를 활용해 WebClient를 만드는 방법이다.
  • 다른 2개의 메서드는 create( ) 활용하여 WebClient를 생성한다.
  • WebClient는 객체를 생성한 후 요청을 전달하는 방식으로 동작한다.
    1. builder( )를 통해 baseUrl( ) 메서드에서 기본 URL을 설정하고 defaultHeader( ) 메서드로 헤더 값을 설정한다.
    2. webClient 객체를 이요할 떄는 객체를 생성한 후에 재사용하는 방식으로 구현하는 것이 좋다.
  • builder( ) 를 사용할 경우 확장할 수 있는 메서드
    1. defaultHeader( ) : WebClient의 기본 헤더 설정
    2. defaultCookie( ) : WebClient의 기본 쿠키 설정
    3. defaultUriVariable( ) : WebClient의 기본 URI 확장값 설정
    4. filter( ) : WebClient에서 발생하는 요청에 대한 필터 설정
  • webClient 복사해서 사용하는 방법
WebClient webClient = WebClient.create("http://localhost:9090");
WebClient clone = WebClient.mutate().build();
  • retrieve( ) 메서드는 bodyToMono( ) 메서드를 통해 리턴 타입을 설정해서 문자열 객체를 받아오게 된다.
  • retrieve( ) 대신 exchange( ) 메서드를 사용이 가능한데 지원이 중단되어 exchangeToMono( ) / exchangeToFlux( )를 사용해야한다.
  • WebClient는 기본적으로 논블로킹(Non-Blocking) 방식으로 동작하기 때문에 기존에 사용하던 코드의 구조를 블로킹 구조로 바꿔줄 필요가 있다.

 

WebClient POST 요청 예제

public ResponseEntity<MemberDto> postWithParamAndBody() {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://localhost:9090")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .build();

    MemberDto memberDto = new MemberDto();
    memberDto.setName("test!!");
    memberDto.setEmail("test@gmail.com");
    memberDto.setOrganization("studio");

    return webClient.post().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "test")
                .queryParam("email", "test@naver.com")
                .queryParam("organization", "test")
                .build())
            .bodyValue(memberDto)
            .retrieve()
            .toEntity(MemberDto.class)
            .block();
}

public ResponseEntity<MemberDto> postWithHeader() {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://localhost:9090")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .build();

    MemberDto memberDto = new MemberDto();
    memberDto.setName("test!!");
    memberDto.setEmail("test@gmail.com");
    memberDto.setOrganization("studio");

    return webClient.post()
            .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                    .build())
            .bodyValue(memberDto)
            .header("my-header", "API")
            .retrieve()
            .toEntity(MemberDto.class)
            .block();
}
  • POST 방식에서 눈여겨볼 내용은 HTTP 바디 값을 담는 방법과 커스텀 헤더를 추가하는 방법이다.
  • webClient에서 post( ) 메서드를 통해 POST 메서드 통신을 정의했고, uri( )는 uriBuilder로 path와 parameter를 설정
  • bodyValue( ) 메서드를 통해 HTTP 바디 값을 설정한다.
  • HTTP 바디에는 일반적으로 데이터 객체(DTO, VO 등) 파라미터로 전달한다.
  • postWithHeader( ) 메서드는 POST 요청을 보낼 때 헤더를 추가해서 보내는 예제이다.
  • 일반적으로 임의로 추가한 헤더에는 외부 API를 사용하기 위해 인증된 토큰값을 담아 전달한다.
반응형

댓글