[Spring] Migrating from HttpClient 4 to HttpClient 5 (for self-signed certificates)

2024. 12. 4. 14:21Java

기존 HttpClient 4 소스

의존성

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.13</version>
</dependency>

 

RestTemplate 설정

package example.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

@Configuration
@Slf4j
public class RestTemplateConfig {

  @Bean
  public HttpClient httpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    // 모든 인증서를 신뢰하도록 설정한다
    final SSLContext sslContext = new SSLContextBuilder()
            .loadTrustMaterial(null, (X509Certificate[] chain, String authType) -> true).build();

    // Https 인증 요청시 호스트네임 유효성 검사를 진행하지 않게 한다.
    final SSLConnectionSocketFactory sslSocketFactory
            = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

    final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslSocketFactory)
            .build();

    final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);

    return HttpClientBuilder.create()
            .setMaxConnTotal(100)
            .setMaxConnPerRoute(80) // the total number of connections limit to a single port or url.
            .setSSLContext(sslContext)
            .setConnectionManager(connectionManager)
            .build();
  }

  @Bean
  HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(final HttpClient httpClient) {
    final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setConnectTimeout(5 * 1_000);
    factory.setReadTimeout(30 * 1_000);
    factory.setHttpClient(httpClient);

    return factory;
  }

  @Bean
  public RestTemplate restTemplate(HttpComponentsClientHttpRequestFactory factory) {
    return new RestTemplate(factory);
  }
}

 

HttpClient 5 소스

의존성

groupId가 다름 주의

<dependency>
  <groupId>org.apache.httpcomponents.client5</groupId>
  <artifactId>httpclient5</artifactId>
  <version>5.4.1</version>
</dependency>

 

RestTemplate 설정

HttpClient에 세팅하던 설정들을 ConnectionManager가 담당하게 됨

package example.config;

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

@Configuration
public class RestTemplateConfig {

  @Bean
  public CloseableHttpClient httpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    return HttpClients.custom()
            .setConnectionManager(
                    PoolingHttpClientConnectionManagerBuilder.create()
                            // .setTlsSocketStrategy(DefaultClientTlsStrategy.createDefault())
                            .setTlsSocketStrategy(
                                    // Https 인증 요청시 호스트네임 유효성 검사를 진행하지 않게 한다.
                                    new DefaultClientTlsStrategy(
                                            new SSLContextBuilder()
                                                    .loadTrustMaterial(null, (chain, authType) -> true)
                                                    .build(),
                                            NoopHostnameVerifier.INSTANCE))
                            .setDefaultConnectionConfig(
                                    ConnectionConfig.custom()
                                            .setSocketTimeout(1500, TimeUnit.MILLISECONDS) // 읽기 시간
                                            .setConnectTimeout(3000, TimeUnit.MILLISECONDS) // 연결 시간
                                            .build())
                            .setMaxConnTotal(100) // 커넥션 풀 적용 (최대 오픈되는 커넥션 수)
                            .setMaxConnPerRoute(10) // 커넥션 풀 적용 (IP:포트 1쌍에 대해 수행할 연결 수 제한)
                            .build())
            .evictIdleConnections(TimeValue.ofSeconds(10))
            .build();
  }

  @Bean
  public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory(final HttpClient httpClient) {
    final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setConnectTimeout(5 * 1_000);
    factory.setReadTimeout(30 * 1_000);
    factory.setHttpClient(httpClient);

    return factory;
  }

  @Bean
  public RestTemplate restTemplate(final HttpComponentsClientHttpRequestFactory clientHttpRequestFactory) {
    return new RestTemplate(clientHttpRequestFactory);
  }
}