Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발새발

WebClient 본문

CS

WebClient

개발하는후추 2022. 10. 5. 17:50

WebClient 발표.key
5.24MB

우리가 개발하는 어플리케이션들을 크게 2개로 나눠보면 요청자와 제공자라고 할 수 있다

요청자를 consumer 또는 subscriber //  제공자를 producer 또는 provider

요청자가 제공자에게 무언가를 요청할 때 제공자가 공개한 API를 이용하게 된다

요청 시 프로그램에서 우리가 가장 흔하게 사용하는 것이 Http Client이다

Spring WebClient는 웹으로 API를 호출하기 위해 사용되는 Http Client 모듈 중 하나이다

Java에서 가장 많이 사용하는 Http Client는 RestTemplate이다

공통점은 둘다 HttpClient모듈이다,

차이점은 통신방법이 RestTemplate은 Blocking방식이고, WebClient는 Non-Blocking방식이다

Non-blocking방식이 필요한 이유는 네트워킹의 병목현상을 줄이고 성능을 향상시키기 위해서다

WebClient는 요청을 나타내고 전송하게 해주는 빌더 방식의 인터페이스를 사용하며,

외부 API로 요청을 할 때 리액티브 타입의 전송과 수신을 한다 (Mono, Flux)

WebClient 특징

  • - 싱글 스레드 방식을 사용
  • - Non-Blocking 방식을 사용
  • - JSON, XML을 쉽게 응답

WebClient를 사용하는 방법

Dependencies

먼저, Webclient 의존성을 추가해줍니다.

gradle
dependencies {
    compile 'org.springframework.boot:spring-boot-starter-webflux'
}

Create

WebClient를 생성하는 방법은 2가지가 있다

아주 단순하게 create()를 하는 방법과, option을 추가할 수 있는 build()를 사용가능 하다

create()

정말 단순하게 WebClient의 디폴트 세팅으로 아래와 같이 생성할 수 있으며, 요청할 uri와 함께 생성할 수도 있다

WebClient.create();
// or
WebClient client = WebClient
                .create("http://api.data.go.kr/openapi/tn_pubr_public_electr_whlchairhgh_spdchrgr_api");

build()

혹은 모든 설정을 customization할 수 있도록 
DefaultWebClientBuilder 클래스을 사용하는 build() 메서드를 사용할 수도 있다
 

Options

- uriBuilderFactory : base url을 커스텀한 UriBuilderFactory

- defaultHeader : 모든 요청에 사용할 헤더

- defaultCookie : 모든 요청에 사용할 쿠키

- defaultRequest : 모든 요청을 커스텀할 Consumer

- filter : 모든 요청에 사용할 클라이언트 필터

- exchangeStrategies : HTTP 메시지 reader & writer 커스터마이징

- clientConnector : HTTP 클라이언트 라이브러리 세팅

WebClient client = WebClient.builder()
  .baseUrl("http://localhost:8080")
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
  .build();

Request

GET

일반적으로 GET API를 사용하여 리소스 모음 또는 단일 리소스를 가져온다

1. Flux 

GET /employees
Request: collection of employees as Flux
@Autowired
WebClient webClient;

public Flux<Employee> findAll() {
	return webClient.get()
		.uri("/employees")
		.retrieve()
		.bodyToFlux(Employee.class);
}

2. Mono

GET /employees/{id}
Request: single employee by id as Mono
@Autowired
WebClient webClient;

public Mono<Employee> findById(Integer id) {
	return webClient.get()
		.uri("/employees/" + id)
		.retrieve()
		.bodyToMono(Employee.class);
}

 

POST

계속해서 body를 포함하는 POST의 예시

POST /employees
Request : creates a new employee from request body
Reponse : returns the created employee
@Autowired
WebClient webClient;

public Mono<Employee> create(Employee empl) {
	return webClient.post()
		.uri("/employees")
		.body(Mono.just(empl), Employee.class)
		.retrieve()
		.bodyToMono(Employee.class);
}

여기서 body는 아래와 같이 정의되어다

body를 보면 Mono.just(empl)이 있으며 empl의 타입이 Employee이라는 클래스 정의

여기서 입력값이 Mono<Employee> 타입이라는 것을 확인할 수 있다

만약, POST를 하고 받을 body가 없다면 Void.class를 입력하면 된다

// Empty body
public Mono<Void> create(Employee empl) {
	return webClient.post()
		.uri("/employees")
		.body(Mono.just(empl), Employee.class)
		.retrieve()
		.bodyToMono(Void.class);
}

 

bodyToMono()는 아래 Response에서 확인하실 수 있다

body(Object producer, Class<?> elementClass);

body(Object producer, ParameterizedTypeReference<?> elementTypeRef);

body(BodyInserter<?, ? super ClientHttpRequest> inserter);

 

요청할 데이터(producer)와 그 타입(elementClass / elementTypeRef)을 명시해주면 됩니다

Response

요청을 한 후에는 응답을 받아 처리

응답을 받을 때에는 아래의 두 가지 메소드 중 적절한 것을 선택해서 사용하면 된다

- retrieve() : body를 받아 디코딩하는 간단한 메소드

- exchange() : ClientResponse를 상태값 그리고 헤더와 함께 가져오는 메소드

exchange()를 통해 세세한 컨트롤이 가능하지만, Response 컨텐츠에 대한 모든 처리를 직접 하면서 메모리 누수 가능성 때문에 retrieve()를 권고

bodyToFlux, bodyToMono 를 위에서 계속해서 봤다

bodyToFlux, bodyToMono 는 가져온 body를 각각 Reactor의 Flux와 Mono 객체로 바꿔준다

Mono 객체는 0-1개의 결과를 처리하는 객체이고, Flux는 0-N개의 결과를 처리하는 객체입니다.

retrieve()

retrieve를 사용한 후의 데이터는 크게 두 가지 형태로 받을 수 있다

✔️  toEntity()

status, headers, body를 포함하는 ResponseEntity 타입으로 받을 수 있습니다.

 Mono<ResponseEntity<Person>> entityMono = client.get()
     .uri("/persons/1")
     .accept(MediaType.APPLICATION_JSON)
     .retrieve()
     .toEntity(Person.class);

 

✔️  toMono() , toFlux()

body의 데이터로만 받고싶다면 아래와 같이 사용할 수 있습니다.
Mono<Person> entityMono = client.get()
    .uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .bodyToMono(Person.class);
 

📌  exchangeToXXXX()

Mono<Person> entityMono = client.get()
    .uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchangeToMono(response -> {
        if (response.statusCode().equals(HttpStatus.OK)) {
            return response.bodyToMono(Person.class);
        }
        else {
            return response.createException().flatMap(Mono::error);
        }
    });

 

exchange() 는 deprecated 된다 //exchangeToXXXX() 메소드를 사용!

Mono<>, Flux<> 사용

아래에서도 확인할 수 있는데, Mono<> Flux<>는 어떻게 사용할 수 있을까요? 

map이나 filter와 같은 메서드를 사용하는 방법이 있으며, 

필자는 아래와 같이 사용했습니다.

Mono<Employee> employeeMono = webClient.get(). ...
employeeMono.block() // or blockOptional()

Flux<Employee> employeeFlux = webClient.get(). ...
employeeFlux.subscribe(employee -> { ... });

 

ErrorHandling

에러 핸들링은 결과 값을 반환받을 때의 상황에 따라 적절히 처리할 수 있다

 retrieve()

retrieve는 1xx, 2xx, 3xx, ... StatusCode 별로 아래와 같이 처리할 수 있다

4xx, 5xx 의 에러코드일 때에만 new RuntimeException으로 처리해주도록 제작

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);

 

exchangeToXXXX() 

exchange를 통해 받은 응답에 대한 statusCode를 이용해 분기처리하여 핸들링할 수 있다

Mono<Object> entityMono = client.get()
       .uri("/persons/1")
       .accept(MediaType.APPLICATION_JSON)
       .exchangeToMono(response -> {
           if (response.statusCode().equals(HttpStatus.OK)) {
               return response.bodyToMono(Person.class);
           }
           else if (response.statusCode().is4xxClientError()) {
               return response.bodyToMono(ErrorContainer.class);
           }
           else {
               return Mono.error(response.createException());
           }
       });

 

참고

Baedung : Spring 5 WebClient

Baedung : RestTemplate

Spring.io : WebReactive

WebClient Get Post Excample

출처: https://gngsn.tistory.com/154 [ENFJ.dev:티스토리]

출처: https://gngsn.tistory.com/154 [ENFJ.dev:티스토리]

https://my-gaebalsaebal.tistory.com/44(예전에 간략하게 정리한 RestTemplate다)

동기 비동기 방식에 대해서는 

https://www.youtube.com/watch?v=oEIoqGd-Sns&t=1s

https://koras02.tistory.com/87

https://happycloud-lee.tistory.com/154?category=902418 

'CS' 카테고리의 다른 글

OAuth 와 JWT의 흐름  (0) 2022.10.27
CI / CD  (0) 2022.10.25
동기 비동기  (0) 2022.09.11
네트워크  (0) 2022.08.03
컴퓨터와 프로그래밍의 이해 - 웹 어플리케이션과 웹 서버  (0) 2022.07.27
Comments