관리 메뉴

나만의공간

WebClient 사용법 (ConnectionProvider 사용법) 본문

IT/JAVA

WebClient 사용법 (ConnectionProvider 사용법)

밥알이 2024. 2. 26. 11:51

Spring에서 제공하는 RestFul API 연결방식은 여러개가 존재 하는데
RestTemplate방식을 주로 사용하다 Feign방식을 사용하고 있었습니다.
Feign방식에서 WebClient 방식으로 변경을 진행 했었는데 진행도중 알수 없는 오류가 발생하여 발생 원인을 해결하고자 많은 시행 착오를 거치게 되었는데 WebClient방식을 사용할때는 상대편 서버 환경을 고려 하여 변경이 필요하거나, 이슈가 안되도록 사전에 설정값을 잘 작성해야 합니다.

WebClient 오류 현상(Connection prematurely closed BEFORE response 에러 대응)

WebClient 방식으로 변경한 후 평상시 잘 통신이 되는데 어느정도 시간이 지나면 아래와 같은 메시지가 나오구 해당 요청건은 오류가 발생하고 다시 재시도 하면 정상적으로 되는 현상이 계속 반복이 됩니다.

동일 기능을 Feign 방식으로 전환을 하면 또 정상적으로 통신이 되고여

오류메시지
Connection prematurely closed BEFORE response; 
nested exception is reactor.netty.http.client.PrematureCloseException: 
Connection prematurely closed BEFORE response

Feign / WebClient 차이점

Feign:

  1. 선언적 API 구성: Feign은 인터페이스를 사용하여 RESTful 서비스의 클라이언트를 정의할 수 있습니다. 이는 선언적인 방식으로 API를 구성할 수 있어 개발자가 명확하게 이해하기 쉽습니다.
  2. Ribbon과의 통합: Feign은 기본적으로 Ribbon과 함께 사용되어 로드 밸런싱과 재시도 등의 기능을 쉽게 구현할 수 있습니다.
  3. 내장된 히스토그램 및 로깅: Feign은 내부적으로 통신 히스토그램을 제공하며, 디버깅 및 모니터링을 위해 로깅을 지원합니다.

WebClient:

  1. 비동기 및 논 블로킹: WebClient는 비동기 및 논 블로킹 방식으로 작동하므로 대용량의 요청을 처리할 때 성능이 향상됩니다.
  2. Reactor 프로젝트와 통합: WebClient는 Reactor 프로젝트의 일부로서, Reactor의 Mono 및 Flux와 통합됩니다. 이는 리액티브 스트림을 사용하여 더 효율적으로 데이터를 처리할 수 있음을 의미합니다.
  3. 더 많은 유연성: WebClient는 더 많은 유연성을 제공합니다. 예를 들어, 요청 및 응답의 다양한 측면을 조정할 수 있는 다양한 옵션을 제공합니다.

기본설정 WebClient코드

    private final int CON_TIMEOUT = 2000; // 연결 대기시간
    private final int RW_TIMEOUT = 3000; // 읽기/쓰기 대기시간
    private final int RES_TIMEOUT = 5000; // 응답 대기시간
    
	// WebClient.Builder를 Bean으로 등록하는 메소드
    @Bean
    public WebClient.Builder primaryWebClientBuilder() {
        return WebClient.builder()
                // HTTP 요청 및 응답을 인코딩 및 디코딩하기 위한 설정
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
                // 모든 요청에 대해 기본적으로 Accept 헤더를 JSON 형식으로 설정
                .defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
                // 모든 요청에 대해 기본적으로 Content-Type 헤더를 JSON 형식으로 설정
                .defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                // Reactor Netty의 HttpClient를 사용하여 커스텀 클라이언트 커넥터를 설정
                .clientConnector(new ReactorClientHttpConnector(
                        // HttpClient를 생성하고 설정
                        HttpClient.create()
                                // 연결 시도 중에 대기하는 최대 시간을 설정
                                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CON_TIMEOUT)
                                // 응답을 받기까지의 최대 시간을 설정
                                .responseTimeout(Duration.ofMillis(RES_TIMEOUT))
                                // 연결이 설정되면 연결된 채널에 대해 설정을 구성하는 람다
                                .doOnConnected(conn -> conn
                                        // 읽기 작업에 대한 최대 시간을 설정하는 핸들러 추가
                                        .addHandlerLast(new ReadTimeoutHandler(RW_TIMEOUT, TimeUnit.MILLISECONDS))
                                        // 쓰기 작업에 대한 최대 시간을 설정하는 핸들러 추가
                                        .addHandlerLast(new WriteTimeoutHandler(RW_TIMEOUT, TimeUnit.MILLISECONDS))
                                )
                        )
                );
    }
  • 코드는 WebClient를 생성하고 설정합니다.
  • 기본적으로 JSON 형식의 요청 및 응답을 사용하도록 헤더를 설정합니다.
  • 커스텀 클라이언트 커넥터를 사용하여 Reactor Netty의 HttpClient를 설정합니다.
  • 연결 시간 제한, 응답 시간 제한 및 읽기/쓰기 작업 시간 제한과 같은 다양한 네트워크 관련 설정을 구성합니다.

변경 WebClient코드

위 오류코드를 해결하기 위한 connectionProvider를 사용하여 상세 정보를 설정

import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.TcpClient;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

public class WebClientConfiguration {

    // WebClient.Builder를 Bean으로 등록하는 메소드
    @Bean
    public WebClient.Builder secondaryWebClientBuilder() {
        // ConnectionProvider를 설정하여 커넥션 풀을 제어합니다.
        ConnectionProvider provider = ConnectionProvider.builder("secondary-provider")
                // 유지할 최대 커넥션 수를 설정합니다.
                .maxConnections(200)
                // 사용하지 않는 상태의 커넥션을 유지하는 시간을 설정합니다.
                .maxIdleTime(Duration.ofSeconds(5))
                // 커넥션 풀에서 커넥션의 최대 수명을 설정합니다.
                .maxLifeTime(Duration.ofSeconds(5))
                // 모든 커넥션이 사용 중일 때 커넥션을 얻기 위해 대기하는 최대 시간을 설정합니다.
                .pendingAcquireTimeout(Duration.ofSeconds(5))
                // 커넥션을 얻기 위해 대기하는 최대 수를 설정합니다.
                .pendingAcquireMaxCount(1000)
                // 만료된 커넥션을 백그라운드에서 제거하는 주기를 설정합니다.
                .evictInBackground(Duration.ofSeconds(5))
                // 커넥션을 마지막에 사용된 순서대로 재사용하는 LIFO(Last In, First Out) 방식을 설정합니다.
                .lifo()
                // ConnectionProvider를 빌드합니다.
                .build();
        
        return WebClient.builder()
                // 인코딩 및 디코딩 설정을 구성합니다.
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
                // 기본적으로 JSON 형식의 Accept 및 Content-Type 헤더를 설정합니다.
                .defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                // Reactor Netty의 HttpClient를 사용하여 커스텀 클라이언트 커넥터를 설정합니다.
                .clientConnector(new ReactorClientHttpConnector(
                        // ConnectionProvider를 사용하여 HttpClient를 생성하고 설정합니다.
                        HttpClient.create(provider)
                                // 연결 시간 제한을 설정합니다.
                                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CON_TIMEOUT)
                                // 응답 시간 제한을 설정합니다.
                                .responseTimeout(Duration.ofMillis(RES_TIMEOUT))
                                // 연결이 설정된 후에 수행할 작업을 설정합니다.
                                .doOnConnected(conn -> conn
                                        // 읽기 작업 시간 제한을 설정합니다.
                                        .addHandlerLast(new ReadTimeoutHandler(RW_TIMEOUT, TimeUnit.MILLISECONDS))
                                        // 쓰기 작업 시간 제한을 설정합니다.
                                        .addHandlerLast(new WriteTimeoutHandler(RW_TIMEOUT, TimeUnit.MILLISECONDS))
                                )
                    )
                );
    }
}
  • 코드는 ConnectionProvider를 사용하여 커넥션 풀을 설정합니다.
  • 각각의 커넥션 풀 관련 설정 (최대 커넥션 수, 최대 유휴 시간, 최대 수명, 대기 시간 등)을 구성합니다.
  • 마지막에 사용된 커넥션을 재사용하는 LIFO 방식을 선택합니다.
  • HttpClient를 생성하고 설정합니다. 연결 시간 제한, 응답 시간 제한, 읽기/쓰기 작업 시간 제한과 같은 네트워크 관련 설정을 구성합니다.

'IT > JAVA' 카테고리의 다른 글

Java Stream Operation 기능 #1  (0) 2024.03.06
Java Stream 소개 #1  (0) 2024.02.27
CheckedException 과 unCheckedException 설명  (0) 2024.02.22
Intellij CheckStyle 적용하기  (0) 2023.03.17
자바 코딩 컨벤션 (Code Style) 적용  (0) 2023.03.10
Comments