[Spring] Web Client 개념 및 사용

WOOK JONG KIM·2022년 11월 8일
2
post-thumbnail
post-custom-banner

일반적으로 실제 운영환경에 적용되는 애플리케이션은 정식 버전으로 출시된 스플이 부트 버전보다 낮아 RestTemplate도 많이 사용하지만 최신 버전은 지원 중단

흐름에 맞추어 WebClient 사용할 것을 권장

Spring WebFlux는 HTTP 요청을 수행하는 클라이언트로 WebClient 제공
-> WebClient는 리액터 기반으로 동작하는 API

리액터 기반 -> 스레드와 동시성 문제를 벗어나 비동기 형식으로 사용 가능

  • 논블로킹 I/O 지원
  • 리액티브 스트림의 백 프래셔 지원
  • 적은 하드웨어 리소스로 동시성을 지원
  • 함수형 API 지원
  • 동기, 비동기 상호작용 지원
  • 스트리밍 지원

WebClient 구성

WebClient를 사용하려면 WebFlux 모듈에 대한 의존성 추가

			<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>

WebClient 사용하기

구현

생성하는 법 크게 2가지

  • create() 메서드를 이용한 생성
  • builder() 메서드를 이용한 생성

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("Flature"))
                .retrieve().toEntity(String.class).block();

        return responseEntity.getBody();
    }

    public String getNameWithParameter(){
        WebClient webClient = WebClient.create("http://localhost:9090");
        
        return webClient.get().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "Flature")
                .build())
                .exchangeToMono(clientResponse -> {
                    if(clientResponse.statusCode().equals(HttpStatus.OK)){
                        return clientResponse.bodyToMono(String.class);
                    } else{
                        return clientResponse.createException().flatMap(Mono::error);
                    }
                })
                .block();
    }
}

첫메서드는 builder()를 활용하였고 다른 두 메서드는 create() 방식을 활용하였음

WebClient는 우선 객체를 생성한 후 요청을 전달하는 방식으로 전달
-> 이후 재사용하는 방식으로 구현하는 것이 좋음

builder() 사용 시 확장할 수 있는 메서드

  • defaultHeader() : WebClient의 기본 헤더 설정
  • defaultCookie() : WebClient의 기본 쿠키 설정
  • defaultVariable() : WebClient의 기본 URI 확장값 설정
  • filter() : WebClient에서 발생하는 요청에 대한 필터 설정

이미 빌드된 WebClient는 변경이 불가하지만, 복사는 가능

WebClient webClient = WebClient.create("http://localhost:9090");
WebClient clone = webClient.mutate().build();

첫 메서드

WebClient는 보다 시피 HTTP 메서드를 get,post 등의 네이밍이 명확한 메서드로 설정이 가능

URI 확장을 위해 uri() 메서드 사용 가능

retrieve() 메서드는 요청에 대한 응답을 받았을 때 그 값을 추출하는 방법
-> bodyToMono() 메서드를 통해 리턴 타입을 설정해서 문자열 객체로 받아오게 되어있음

Mono는 리액티브 스트림에 대한 선행이 필요한 개념으로 Flux와 비교됨
-> Publisher의 구현체

block()은 기본적으로 논블로킹으로 작동하는 WebClient를 블로킹 구조로 바꾸기 위해 사용

두번째 메서드

PathVariable 값을 추가해 요청을 보내는 예제

uri() 메서드 내부에 uriBuilder를 사용해 path를 설정하고 build() 메서드에 추가 값을 넣는 것으로 PathVariable 추가 가능

ResponseEntity<String> responseEntity1 = webClient.get()
	.uri("/api/v1/crud-api/{name}", "Flature")
    .retrieve()
    .toEntity(String.class)
    .block()

위와 같이도 작성 가능

세번째 메서드

쿼리 파라미터를 함께 전달하는 역할

쿼리 파라미터 요청을 담기 위해 uriBuilder를 사용하며, queryParam() 메서드를 사용해 전달하려는 값 전달

retrieve 메서드 대신 exchange() 사용하였음
-> 응답 결과 코드에 따라 다르게 응답 설정 가능
-> 위 코드에서는 if문을 통해 상황에 따라 결과값을 다르게 전달할 수 있게 하였음


POST 요청 예제

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

        MemberDto memberDto = new MemberDto();
        memberDto.setName("flature!!");
        memberDto.setEmail("flature@gmail.com");
        memberDto.setOrganization("Around Hub Studio");

        return webClient.post().uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                .queryParam("name", "Flature")
                .queryParam("email", "flature@wikibooks.co.kr")
                .queryParam("organization", "Wikibooks")
                .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("flature!!");
        memberDto.setEmail("flature@gmail.com");
        memberDto.setOrganization("Around Hub Studio");

        return webClient
                .post()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/add-header")
                        .build())
                .bodyValue(memberDto)
                .header("my-header", "Wikibooks API")
                .retrieve()
                .toEntity(MemberDto.class)
                .block();
    }

GET 요청을 만드는 방법과 다르지 않지만 HTTP 바디 값을 담는 방법과, 커스텀 헤더를 추가하는 방법을 보자

uriBuilder로 path와 parameter를 설정하였고 그 후 bodyValue를 통해 HTTP 바디 값을 설정 함
-> 바디에는 일반적으로 데이터 객체(DTO, VO)를 파라미터로 전달

header() 메서드를 사용해 헤더에 값을 추가하였음
-> 일반적으로 인증된 토근값을 담아 전달

profile
Journey for Backend Developer
post-custom-banner

0개의 댓글