Spring Boot 3.x 업그레이드 시 Apache HttpClient 의존성 문제

chiyongs·2024년 2월 26일
4

프로젝트를 Java 21 및 Spring Boot 3.X 버전으로 업그레이드하다 여러 에러들을 만났지만 그 중 RestTemplate을 커스텀하여 Spring Bean으로 등록하는 코드에서 발생한 에러를 해결한 과정에 대해서 공유드리려 합니다!

Spring Boot 3.x 와 Apache HttpClient

RestTemplate Bean 설정 예시 코드

static final int READ_TIMEOUT = 1500;
static final int CONN_TIMEOUT = 3000;

@Bean
public RestTemplate restTemplate() {
	var factory = new HttpComponentsClientHttpRequestFactory();
	factory.setReadTimeout(READ_TIMEOUT);
	factory.setConnectTimeout(CONN_TIMEOUT);
	factory.setHttpClient(HttpClientBuilder.create().build());
    return new RestTemplate(factory);
}

위와 같은 예시 코드는 Spring Boot 3.X 버전에서 동작하지 않습니다.
이유 : Spring Framework 6.0 버전부터 Apache HttpClient에 대한 지원이 제거되면서 org.apache.httpcomponents.client5:httpclient5 라이브러리로 교체되었습니다.
(기존 라이브러리와 groupId가 다릅니다!)
(https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide#apache-httpclient-in-resttemplate)

아래는 프로젝트에서 사용하던 spring-web-5.3.19 버전의 org.springframework.http.client.HttpComponentsClientHttpRequestFactory의 setReadTimeout 메서드입니다.

// org.springframework.http.client.HttpComponentsClientHttpRequestFactory
public void setReadTimeout(int timeout) {
	Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
	this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
}

setReadTimeout 메서드는 내부적으로 requestConfigBuilder, 즉 org.apache.http.client.config.RequestConfig 클래스 내부의 Builder 클래스를 가리킵니다.
결국, RequestConfig 클래스 내 socketTimeout 변수를 설정하는 것입니다.

하지만, 버전 업그레이드 시 Apache HttpClient에 대한 지원이 제거되어 새로운 라이브러리로 교체해보면 HttpComponentsClientHttpRequestFactory의 setReadTimeout 메서드가 존재하지 않는 것을 확인할 수 있습니다.

그렇다면 어떻게 해야 할까요?
아직 Spring Boot 3.X 버전이 많이 사용되지 않아 해당 이슈를 동일하게 겪는 경우를 찾기 힘들었습니다.
그래서 Apache HttpClient 공식문서를 찾아봤습니다.

다행히도 공식문서에서 Apache HttpClient 4.x 버전에서 5.x 버전으로의 마이그레이션 관련 문서를 발견했습니다.
공식문서 : https://hc.apache.org/httpcomponents-client-5.3.x/migration-guide/preparation.html

마이그레이션 방법

공식문서에서는 Apache HttpClient 4.x 버전에서 가장 좋은 예시와 흔히 사용되는 패턴으로 코드를 먼저 정비하는 것을 추천합니다.
그렇게 정비된 코드를 Apache HttpClient 5.x 로 쉽게 마이그레이션하도록 도와줍니다.

https://hc.apache.org/httpcomponents-client-5.3.x/migration-guide/preparation.html
공식 문서를 참고하여 예시 코드를 변경해보겠습니다.
(공식 문서에서 여러 상황에 대해 자세히 문서화되어있으니 꼭 참고하시길 바랍니다!!)

static final int READ_TIMEOUT = 1500;
static final int CONN_TIMEOUT = 3000;

@Bean
public RestTemplate restTemplate() {
	var factory = new HttpComponentsClientHttpRequestFactory();
	factory.setHttpClient(HttpClientBuilder.create().build());
    return new RestTemplate(factory);
}

private HttpClient createHttpClient() {
	return org.apache.hc.client5.http.impl.classic
    .HttpClientBuilder.create()
                      .setConnectionManager(createHttpClientConnectionManager())
                      .build())
                      .build();
}

private HttpClientConnectionManager createHttpClientConnectionManager() {
	return PoolingHttpClientConnectionManagerBuilder.create()
                                                    .setDefaultConnectionConfig(ConnectionConfig.custom()
                                                                                                .setSocketTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
                                                                                                .setConnectTimeout(CONN_TIMEOUT, TimeUnit.MILLISECONDS)
                                                                                                .build())
                                                    .build();
}

HttpComponentsClientHttpRequestFactory 클래스로 readTimeout과 connectTimeout을 바로 설정하던 코드를 HttpClientConnectionManager를 통해 timeout 관련 설정을 하고 이를 HttpClient에 주입하여 기존 설정을 유지할 수 있게 되었습니다.

Spring Boot 3.X 업그레이드 시 Apache HttpClient 의존성으로 발생할 수 있는 이슈에 대해 알아보았습니다.
도움이 되셨으면 좋겠습니다!
감사합니다 😀

2개의 댓글

comment-user-thumbnail
2024년 5월 20일

포스팅 잘 봤습니다!
factory.setHttpClient(HttpClientBuilder.create().build());
요 부분에 setHttpClient 메소드의 인자로 아래에 정의한 createHttpClient() 메소드가 들어가야하는건 아닌지 궁금합니다!

답글 달기
comment-user-thumbnail
2024년 5월 24일

좋은 정보 잘 보고 갑니다.
감사합니다!

그리고

org.apache.hc.client5.http.impl.classic
.HttpClientBuilder.create()
.setConnectionManager(createHttpClientConnectionManager())
.build())
.build();

이 부분 오타 난거 같아요

답글 달기