Webclient

SangLog·2023년 5월 15일
0

기본상식

목록 보기
1/8
post-thumbnail

Webclient 란?

Java 프로그래밍을 하면서 http 외부 통신을 위해서 사용하는 대표적인 방법은
RestTemplat과 Webclient가 있다. 둘 사이에는 동기인지 비동기인지, 멀티스레드인지 아닌지 등의 차이점이 있는데 최근 추세는 Webclient를 더 많이 사용하는것으로 알고 있다. 따라서 금일 포스팅은 Webclient가 무었인지 에 대한 설명을 하고자 한다.

Webclient 특징

  • Non-Blocking
    - 비동기 방식으로 결과값을 기다리지 않고 처리 한다.
    - weblient 에서도 block을 통해서 동기식으로 활용할 수 있다.
  • 싱글 스레드 방식
    - 아래의 그림과 같이 webclient 당 하나의 스레드가 동작하면서 처리한다.
  • Reactor 기반의 Functional API (Mono, Flux)
  • Spring 5.0 이상에서 지원


이미지 출처

기본 설정

Webclient 를 사용하기 위해서는 webflux 의존성이 필요하다.

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

Webclient 생성

Webclient는 create() 통해서 Static 하게 바로 호출을 해서 사용하는 방법을 사용하거나 builder를 통해서 설정들을 customization 하고 해당하는 Bean을 만들어서 재활용해서 사용하는등의 두 가지 방법 이있다.

// 기본 Option없이 간단하게 생성하여 사용 
WebClient.create("http://localhost:8080").get().uri("/test").retrieve()

// 아래와 같이 다양한 설정들을 추가적으로 만들어 줄 수 있다. 
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();

webclient는 한번 build 하면 상태를 변경 할 수 없다

Webclient Option

  • baseUrl : 호출을 하고자 하는 기본 Url 값 입력 (Host 정보)
  • uriBuilderFactory : base Url 과 인코딩 타입등 조금 더 커스텀된 url 셋팅이라고 볼 수 있다.
  • defaultHeader : 모든 요청에 사용할 헤더
  • defaultCookie : 모든 요청에 사용할 쿠키
  • defaultRequest : 모든 요청을 custom
  • filter : 모든 요청에 사용할 클라이언트 필터
  • exchangeStrategies : HTTP 메시지 reader & writer 커스텀 (ex, 버퍼 size)
  • clientConnector : Reactor netty 설정 custom (ex, HTTP 클라이언트 라이브러리 세팅)

ClientConnector

HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

WebClient webClient = WebClient.builder()
      .clientConnector(new ReactorClientHttpConnector(httpClient))
      .build();
Resources

위와 같이 Http 통신에 대한 추가적인 설정을 HttpClient 클레스를 만들어서 넣어주면서 Webclient 에 설정해줄 수 있다.

  • client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000))
    - 커넥션 타임아웃 설정, TCP 연결을 시도하는데 - 초를 시도하고 그 이후에는 실패로 간주
  • client.doOnConnected(conn -> conn
    .addHandlerLast(new ReadTimeoutHandler(10))
    .addHandlerLast(new WriteTimeoutHandler(10))))
    - doOnConnected는 연결이 성사 된 이후의 처리, read는 데이터를 읽는데의 최대 타임아웃 설정 write는 쓰기 작업즉 데이터를 보내는 시간의 타임아웃 설정
  • responseTimeout(Duration.ofMillis(5000))
    - 서버로 부터 응답 받는데 - 초가 지나면 실패로 간주

ConnectionProvider

Connection Pool은 클라이언트와 서버간에 연결을 맺어 놓은 상태(3 Way HandShake 완료상태)를 여러개 유지 하고 필요할때 하나씩 사용하고 반납하는 형태를 말한다. 이렇게 함으로 연결/연결해제에 필요한 HandShake를 하지 않고 더 빠르게 데이터를 주고 받을 수 있다.

ConnectionProvider은 이러한 pool을 생성하기 위한 설정을 하는 클래스 이다.

ConnectionProvider provider = ConnectionProvider.builder("custom-provider")
    .maxConnections(100)
    .maxIdleTime(Duration.ofSeconds(58))
    .maxLifeTime(Duration.ofSeconds(58))
    .pendingAcquireTimeout(Duration.ofMillis(5000))
    .pendingAcquireMaxCount(-1)
    .evictInBackground(Duration.ofSeconds(30))
    .lifo()
    .metrics(true)
    .build();
 
webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create(provider)
            .metrics(true, "custom-api")
            .tcpConfiguration(tcpClient -> tcpClient
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
                .doOnConnected(connection -> connection
                    .addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
                    .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS))
                )
            )
    ))
    .build();

별도의 설정을 하지 않으면 기본 값을 가지고 webclient 가 build 된다.

파라미터

  • maxConnections : 유지할 Connection Pool의 수
    - 기본값 : max(프로세서수, 8) * 2
    • max 값 많큼 미리 생성해 놓지 않고 필요할때마다 생성한다. 말 그대로 최대 생성가능한 수.
  • maxIdleTime : 사용하지 않는 상태(idle)의 Connection이 유지되는 시간.
    - 기본값 : 무제한(-1)
    - 외부 호출을 하는경우 호출하는 쪽의 유지시간과 받는쪽 유지시간 차이로인해서 연결 문제 발생 할 수 있다(그래서 호출하는 쪽이 더 낮은 시간값을 셋팅해야한다)
  • maxLifeTime : Connection Pool 에서의 최대 수명 시간
    - 기본값 : 무제한(-1)
  • pendingAcquireTimeout : Connection Pool에서 사용할 수 있는 Connection 이 없을때 (모두 사용중일때) Connection을 얻기 위해 대기하는 시간
    - 기본값 : 45초
  • pendingAcquireMaxCount : Connection을 얻기 위해 대기하는 최대 수
    - 기본값 : 무제한(-1)
  • evictInBackground : 백그라운드에서 만료된 connection을 제거하는 주기
  • lifo : 마지막에 사용된 커넥션을 재 사용, fifo – 처음 사용된(가장오래된) 커넥션을 재 사용
  • metrics : connection pool 사용 정보를 actuator metric에 노출

Webclient Response

webclinet 에서 응답 값을 받아오는 것에는 두가지 방법이 있다.

  • retrieve()
    - body를 받아서 디코딩 하는 방법
  • exchangeToXXXX()
    - ClientResponse를 상태값 그리고 header랑 가져온다.
    • header 정보등과 응답 상태로 세부 컨트롤이 가능하지만 메모리 누수 가능성이 있기 때문에 retrieve()를 더 권장한다.
Mono<Person> result = client.get()
      .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
      .retrieve()
      .bodyToMono(Person.class);

에러 처리를 하기 위해서는 아래와 같이 onStatus를 활용해서 응답 코드 별로 exception 처리를 할 수 있다.

Mono<Person> result = client.get()
    .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .onStatus(HttpStatus::is4xxClientError, response -> ...)

응답을 받아서 처리하는 부분은 아래와 같이 두가지 방법으로 처리할 수 있다.

  • toEntity
    를 통해서 status, headers, body를 포함하는 ResponseEntity 타입으로 받기
  • bodyToMono/bodyToFlux로 body의 데이터만 받기
// case 1
Mono<ResponseEntity<Person>> entityMono = client.get()
     .uri("/persons/1")
     .accept(MediaType.APPLICATION_JSON)
     .retrieve()
     .toEntity(Person.class);
     
// case 2     
Mono<Person> entityMono = client.get()
    .uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .bodyToMono(Person.class);

호출 하단부에 block()를 입력하면 동기로 호출이 동작하게된다.





참고 블로그
참고 블로그 2
참고 블로그 3
참고 블로그 4

profile
기록 쌓기

0개의 댓글