밸덩의 [Apache HttpClient vs. CloseableHttpClient] 튜토리얼을 번역하며 공부한 내용을 기록한 일지 입니다.
https://www.baeldung.com/apache-httpclient-vs-closeablehttpclient
Apache HttpClient vs. CloseableHttpClient | Baeldung
Look at the difference between Apache HttpClient API's HttpClient and CloseableHttpClient
www.baeldung.com
CloseableHttpClient를 정리하기에 앞서 Java 에서 외부 api를 호출할 때는 여러 방법이 있습니다.
이해하기 쉽게 기준을 크게 두가지로 나눈다면 의존성 추가가 필요한지, 아닌지로 나눌 수 있을 것 같은데요,
JDK 기본 제공 하여 라이브러리 추가 없이 사용 가능 한지 or 라이브러리 추가해서 사용하는지 입니다. 예를 들어 Java SE 표준 라이브러리로, 라이브러리 추가 없이 JAVA 소스코드에서 사용 가능한 HttpURLConnection 과, 이 포스팅에서 설명할 CloseableHttpClient 가 있습니다.
HttpURLConnection | CloseableHttpClient | |
라이브러리 추가 | 없음 (JDK 기본 제공) | 필요 (Apache HttpClient 라이브러리) |
Maven/Gradle로 의존성 | 없음 | 필요 |
기능 확장성 | 제한적 - 최대 커넥션 수 , 커넥션 유지 시간등을 직접 제어할 수 없음 |
확장 용이, 커넥션 풀 설정 가능 - PoolingHttpClientConnectionManager 사용 |
실제 서비스 사용 | 간단한 용도 | 대부분의 실무에서 사용 가능 |
CloseableHttpClient 에 대해 공부하다 보니 외부 API를 호출하는 방법들이 궁금해져서 어떤 경우에 사용하는지를 간단히 정리해봤습니다. 저 같은 경우는 6,7번을 빼고는 수행했던 프로젝트 소스코드에서 모두 봤던 방법들이었습니다. 각 방법에 대한 특징을 알아보고 나니 커넥션 풀 크기 설정 등 멀티스레드 환경에서 사용하기 적합하느냐 아니냐에 따라서 사용법을 나눌 수 있을 것 같습니다.
✅ Java에서 외부 API를 호출하는 대표적인 방법
방법 | 설명 | 사용 권장 상황 |
1. HttpURLConnection | JDK 기본 HTTP API | 라이브러리 추가가 어려운 경우, 아주 간단한 요청 |
2. Apache HttpClient (CloseableHttpClient) |
커넥션 풀, 헤더/타임아웃/SSL 등 고급 기능 | 복잡한 HTTP 처리, 안정적인 통신 |
3. Spring RestTemplate | Spring에서 제공하는 고수준 HTTP API | Spring 기반 프로젝트에서 REST 호출 |
4. Spring WebClient (WebFlux) |
비동기 & 논블로킹 HTTP 클라이언트 | 반응형 또는 비동기 통신 필요시 |
5. Java 11 HttpClient | Java 11부터 도입된 새로운 HTTP 클라이언트 | 최신 Java 사용 시 적합, 비동기 지원 |
6. OkHttp (Square 사 제공) | 가볍고 빠른 HTTP 클라이언트 라이브러리 | Android, 경량 서버, 고성능 통신 |
7. Feign (Spring Cloud) | 선언형 HTTP 클라이언트 | 마이크로서비스 환경에서 REST 클라이언트를 인터페이스 기반으로 구성하고 싶을 때 |
오늘은 그 중 Apache HttpClient의 CloseableHttpClient 에 대해서 정리해 봤습니다.
내용 정리에 앞서 예제소스를 첨부합니다.
* 실무에서 커스텀 하여 사용한 예제 소스
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class HttpClientConfiguration {
private static final int MAX_TOTAL_CONNECTIONS = 100;
private static final int MAX_CONNECTIONS_PER_ROUTE = 20;
private static final int CONNECT_TIMEOUT = 30000;
private static final int CONNECTION_REQUEST_TIMEOUT = 10000;
private static final int SOCKET_TIMEOUT = 60000;
public static CloseableHttpClient createHttpClient() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
connectionManager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
return HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
}
* 호출부
public void sendPostRequest(String token, String jsonPayload) {
String url = "http://test.com/";
try (CloseableHttpClient client = HttpClientConfiguration.createHttpClient()) {
HttpPost post = new HttpPost(url);
post.setHeader("Content-Type", "application/json");
post.setHeader("Authorization", "Bearer " + token);
post.setEntity(new StringEntity(jsonPayload, StandardCharsets.UTF_8)); // 요청 본문에 JSON 문자열을 UTF-8 인코딩으로 설정
try (CloseableHttpResponse response = client.execute(post)) {
// 응답 본문을 UTF-8로 읽어 문자열로 변환
String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
logger.info("응답 바디: {}", responseBody);
}
} catch (Exception e) {
logger.error("HTTP POST 요청 중 오류 발생", e);
}
}
1. Overview
Apache HttpClient is a popular Java library providing efficient and feature-rich packages implementing the client-side of the most recent HTTP standards. The library is designed for extension while providing robust support for the base HTTP methods.
Apache HttpClient는 최신 HTTP 표준의 클라이언트 측을 구현하는, 효율적이고 다양한 기능을 갖춘 패키지를 제공하는 인기 있는 Java 라이브러리입니다. 이 라이브러리는 기본적인 HTTP 메서드에 대한 강력한 지원을 제공하면서도 확장이 용이하도록 설계되었습니다.
In this tutorial, we’ll look at the Apache HttpClient API design. We’ll explain the difference between HttpClient and CloseableHttpClient. In addition, we’ll check how to create CloseableHttpClient instances using HttpClients or HttpClientBuilder.
이 튜토리얼에서는 Apache HttpClient API의 설계에 대해 살펴보겠습니다. 또한 HttpClient와 CloseableHttpClient의 차이점을 설명하고, HttpClients나 HttpClientBuilder를 사용하여 CloseableHttpClient 인스턴스를 생성하는 방법도 알아보겠습니다.
Finally, we’ll recommend which of the mentioned APIs we should be using in our custom code. Also, we’ll look at which API classes implement the Closeable interface, thus requiring us to close their instances in order to free up resources.
마지막으로, 사용자 정의 코드에서 어떤 API를 사용하는 것이 좋은지 권장할 것입니다. 또한, 어떤 API 클래스들이 Closeable 인터페이스를 구현하고 있는지도 살펴보며, 이러한 인스턴스들은 자원을 해제하기 위해 반드시 닫아야 한다는 점을 설명하겠습니다.
2. API Design
we’ll show a part of the API required for the classic execution of HTTP requests and processing HTTP responses: In addition, Apache HttpClient API also supports asynchronous HTTP request/response exchange, as well as reactive message exchange using RxJava.
HTTP 요청을 전통적인 방식으로 실행하고, HTTP 응답을 처리하는 데 필요한 API의 일부를 보여줍니다. 또한, Apache HttpClient API는 비동기 HTTP 요청/응답 처리뿐만 아니라, RxJava를 이용한 반응형 메시지 교환도 지원합니다
3. HttpClient vs. CloseableHttpClient
HttpClient is a high-level interface that represents the basic contract for HTTP request execution. It imposes no restrictions on the request execution process. Also, it leaves specifics like state management and authentication and redirects to individual client implementations.
We can cast any client implementation to the HttpClient interface. Thus, we may use it to execute basic HTTP requests via the default client implementation:
HttpClient는 HTTP 요청 실행을 위한 기본적인 계약을 나타내는 상위 수준의 인터페이스입니다. 이 인터페이스는 요청 실행 과정에 어떤 제약도 두지 않습니다. 또한, 상태 관리(state management), 인증(authentication), 리디렉션(redirect)과 같은 세부 사항은 각 클라이언트 구현체에 맡깁니다.
우리는 어떤 클라이언트 구현체든지 HttpClient 인터페이스로 형변환(casting) 할 수 있습니다. 따라서 기본 클라이언트 구현체를 통해 간단한 HTTP 요청을 실행하는 데 이 인터페이스를 사용할 수 있습니다.
*represents : 나타낸다
* execution : 실행
// 기본 구현체(Default implementation)를 사용하여 HttpClient 인스턴스를 생성
HttpClient httpClient = HttpClients.createDefault();
// 호출할 HTTP GET 요청을 생성합니다.
String serviceOneUrl = 'https://www.test.com/';
HttpGet httpGet = new HttpGet(serviceOneUrl);
// HttpClient를 사용하여 요청을 실행
// execute 메서드는 응답을 처리하기 위한 콜백 함수 (lambda)를 받습니다.
httpClient.execute(httpGet, response -> {
// 응답 코드가 200 OK인지 확인
assertThat(response.getCode()).isEqualTo(HttpStatus.SC_OK);
// 응답 객체를 반환 (필요에 따라 다른 처리도 가능)
return response;
});
Notice here the second parameter of the execute method is an HttpClientResponseHandler functional interface. However, the code above will result in a blocker issue on SonarQube. The reason is that the default client implementation returns an instance of CloseableHttpClient, which requires closing. CloseableHttpClient is an abstract class that represents a base implementation of the HttpClient interface. However, it also implements the Closeable interface. Thus, we should close all its instances after use. We can close them by using either try-with-resources or by calling the close method in a finally clause:
여기서 execute 메서드의 두 번째 매개변수가 HttpClientResponseHandler라는 함수형 인터페이스라는 점에 주목하세요.
하지만 위 코드는 SonarQube에서 blocker(차단) 이슈를 발생시킬 수 있습니다. 그 이유는 기본 클라이언트 구현체가 CloseableHttpClient의 인스턴스를 반환하기 때문이며, 이 인스턴스는 반드시 닫아줘야 하기 때문입니다. CloseableHttpClient는 HttpClient 인터페이스의 기본 구현을 나타내는 추상 클래스입니다. 동시에 Closeable 인터페이스도 구현하고 있습니다. 따라서 이 클래스의 인스턴스는 사용 후 반드시 닫아야 합니다.
이를 위해서는 try-with-resources 문법을 사용하거나, finally 블록에서 close() 메서드를 호출해서 닫아줄 수 있습니다.
// try-with-resources 문법을 사용하여 CloseableHttpClient 인스턴스를 생성
// 이 구문은 try 블록이 끝나면 자동으로 httpClient.close()를 호출해 자원을 해제함
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(serviceOneUrl);
httpClient.execute(httpGet, response -> {
assertThat(response.getCode()).isEqualTo(HttpStatus.SC_OK);
return response;
});
} // try 블록 종료 시 httpClient가 자동으로 닫혀 자원이 반환됨
Therefore, in our custom code, we should use the CloseableHttpClient class, not the HttpClient interface.
따라서, 우리가 직접 작성하는 코드에서는 HttpClient 인터페이스 대신 CloseableHttpClient 클래스를 사용하는 것이 좋습니다.
💡 정리하자면
- 연결 풀이나 타임아웃 설정 등 고급 기능을 쓰려면 구현체가 필요하고,
- CloseableHttpClient는 가장 유연하고 설정 가능한 구현 클래스이기 때문에 사용합니다.
- CloseableHttpClient는 사용 후 반드시 닫아야 하며, try-with-resources 문법을 사용할 수 있습니다.
4. HttpClients vs. HttpClientBuilder
In the above examples, we used a static method from the HttpClients class to obtain a default client implementation. HttpClients is a utility class containing factory methods for creating CloseableHttpClient instances:
위 예제들에서는 기본 클라이언트 구현체를 얻기 위해 HttpClients 클래스의 정적(static) 메서드를 사용했습니다.
HttpClients는 CloseableHttpClient 인스턴스를 생성하는 팩토리(factory) 메서드를 포함한 유틸리티 클래스입니다.
CloseableHttpClient httpClient = HttpClients.createDefault();
We can achieve the same using the HttpClientBuilder class. HttpClientBuilder is an implementation of the Builder design pattern for creating CloseableHttpClient instances:
동일한 작업을 HttpClientBuilder 클래스를 사용해서도 수행할 수 있습니다.
HttpClientBuilder는 CloseableHttpClient 인스턴스를 생성하기 위한 **빌더 디자인 패턴(Builder Design Pattern)**의 구현체입니다.
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
커스텀 해서 사용한 실무 예제 소스
Internally, HttpClients uses HttpClientBuilder to create client implementation instances. Therefore, we should prefer to use HttpClients in our custom code. Given that it is a higher-level class, its internals might change with new releases.
내부적으로 HttpClients는 클라이언트 구현 인스턴스를 생성하기 위해 HttpClientBuilder를 사용합니다.
따라서 우리가 직접 작성하는 코드에서는 HttpClients를 사용하는 것이 더 좋습니다.
HttpClients는 더 상위 수준의 클래스이므로, 그 내부 구현은 새로운 버전에서 변경될 수 있습니다.
💡 정리하자면
- HttpClientBuilder는 세부 설정을 원할 때 사용하고,
- HttpClients.createDefault() 같은 간단한 팩토리 메서드를 쓸 수도 있다.
5. Resource Management
The reason why we need to close CloseableHttpClient instances once they go out of scope is to shut down the associated connection manager.
CloseableHttpClient 인스턴스가 범위를 벗어나면 이를 닫아야 하는 이유는,
연결 관리자(connection manager) 와 연결된 리소스를 종료(shutdown)하기 위함입니다.
5.1. Automatic resource deallocation (HttpClient 4.x)
In the current Apache HttpClient 5 version, client resources are deallocated automatically after the http communication, by using the HttpClientResponseHandler we saw earlier. Before the current version, CloseableHttpResponse is provided for backward compatibility with HttpClient 4.x.
현재 Apache HttpClient 5 버전에서는, 앞에서 살펴본 HttpClientResponseHandler를 사용함으로써
HTTP 통신이 끝난 후 클라이언트 리소스가 자동으로 해제됩니다. 이전 버전과의 호환을 위해, CloseableHttpResponse 클래스는 HttpClient 4.x와의 하위 호환성(backward compatibility)을 목적으로 제공됩니다.
CloseableHttpResponse is a class implementing the ClassicHttpResponse interface. However, ClassicHttpResponse also extends HttpResponse, HttpEntityContainer, and Closeable interfaces.
CloseableHttpResponse는 ClassicHttpResponse 인터페이스를 구현한 클래스입니다.
그리고 ClassicHttpResponse는 HttpResponse, HttpEntityContainer, Closeable 인터페이스를 상속합니다.
The underlying HTTP connection is held by the response object to allow the response content to be streamed directly from the network socket. Therefore, we should use the CloseableHttpResponse class instead of the HttpResponse interface in our custom code. We also need to make sure to call the close method once we consume the response:
응답 객체는 응답 본문(response content)을 네트워크 소켓에서 직접 스트리밍할 수 있도록 하기 위해,
내부 HTTP 연결을 보유하고 있습니다. 따라서, 우리가 직접 작성하는 코드에서는 HttpResponse 인터페이스 대신 CloseableHttpResponse 클래스를 사용하는 것이 좋습니다. 또한, 응답을 다 소비(consume)한 후에는 반드시 close() 메서드를 호출해서 연결을 정리해주어야 합니다.
🧠 요약 정리
HttpClientResponseHandler | 응답 핸들러를 통해 자동 자원 해제 (HttpClient 5부터) |
CloseableHttpResponse | 직접 응답 객체를 다룰 때 사용하는 클래스 |
close() 호출 | 응답 데이터 사용 후 반드시 자원 해제를 위해 호출해야 함 |
// try-with-resources를 사용해 CloseableHttpClient 생성 (자동으로 close됨)
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
// 요청을 보낼 대상 URL을 설정한 HttpGet 객체 생성
HttpGet httpGet = new HttpGet(serviceUrl);
// 또 다른 try-with-resources로 CloseableHttpResponse 생성 (역시 자동 close됨)
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
// 응답에서 본문(HttpEntity)을 꺼냄
HttpEntity entity = response.getEntity();
// 본문 내용을 모두 소비함으로써 연결을 안전하게 종료 (스트림을 끝까지 읽음)
EntityUtils.consume(entity);
}
}
We should note that the underlying(근복적인) connection cannot be safely re-used when response content is not fully consumed. In such situations, the connection will be shut down and discarded by the connection manager.
응답 본문(response content)을 완전히 소비하지 않으면, 해당 연결은 안전하게 재사용될 수 없습니다.
이런 경우에는 연결 관리자가 해당 연결을 종료하고 버리게 됩니다.
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("https://example.com");
try (CloseableHttpResponse response = httpClient.execute(request)) {
// 본문을 읽지 않음 ⇒ 연결이 불완전한 상태로 남음
// => 재사용 불가능, 연결은 종료되고 버려짐
// (EntityUtils.consume() 또는 getEntity().getContent().close() 생략)
}
// 두 번째 요청 – 연결 풀에서 재사용 불가, 새 연결 생성
try (CloseableHttpResponse response2 = httpClient.execute(new HttpGet("https://example.com"))) {
System.out.println("두 번째 요청 성공: " + response2.getCode());
}
}
5.2. Reusing Clients
Closing a CloseableHttpClient instance and creating a new one for every request could be an expensive operation. Instead, we can reuse a single instance of a CloseableHttpClient for sending multiple requests:
매 요청마다 CloseableHttpClient 인스턴스를 닫고 새로 생성하는 것은 비용이 많이 드는 작업일 수 있습니다.
대신 하나의 CloseableHttpClient 인스턴스를 재사용하여 여러 요청을 보내는 것이 좋습니다.
=> 처음 만든 CloseableHttpClient 를 재사용 하라는 뜻.
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
// 첫 번째 요청 URL을 사용해 HttpGet 객체 생성
HttpGet httpGetOne = new HttpGet(serviceOneUrl);
// 첫 번째 요청 실행 및 응답 처리
httpClient.execute(httpGetOne, responseOne -> {
// 응답 본문을 HttpEntity로 가져옴
HttpEntity entityOne = responseOne.getEntity();
// 응답 스트림을 모두 소비하여 연결 재사용 가능하도록 함
EntityUtils.consume(entityOne);
// 응답 상태 코드가 200(성공)인지 검증
assertThat(responseOne.getCode()).isEqualTo(HttpStatus.SC_OK);
// 처리된 응답 객체 반환
return responseOne;
});
// 두 번째 요청 URL을 사용해 HttpGet 객체 생성
HttpGet httpGetTwo = new HttpGet(serviceTwoUrl);
// 두 번째 요청 실행 및 응답 처리
httpClient.execute(httpGetTwo, responseTwo -> {
// 두 번째 응답 본문을 HttpEntity로 가져옴
HttpEntity entityTwo = responseTwo.getEntity();
// 응답 스트림을 모두 소비하여 연결 재사용 가능하도록 함
EntityUtils.consume(entityTwo);
// 응답 상태 코드가 200(성공)인지 검증
assertThat(responseTwo.getCode()).isEqualTo(HttpStatus.SC_OK);
// 처리된 응답 객체 반환
return responseTwo;
});
} // try-with-resources 구문 종료 시 httpClient가 자동으로 close() 호출되어 자원 해제
6. Conclusion
In this article, we explored the classic HTTP API of Apache HttpClient, a popular client-side HTTP library for Java.
We learned the difference between HttpClient and CloseableHttpClient. Also, we recommended using CloseableHttpClient in our custom code. Next, we saw how to create CloseableHttpClient instances using HttpClients or HttpClientBuilder.
Finally, we looked at CloseableHttpClient and CloseableHttpResponse classes, both implementing the Closeable interface. We saw that their instances should be closed in order to free up resources.
'BackEnd > JAVA' 카테고리의 다른 글
서블릿 컨테이너의 ServletContext 동작 방식 / HttpSession (1) | 2025.01.20 |
---|---|
[JAVA] Thread pool 을 위한 ExecutorService 의 생성과 submit, shutdown (0) | 2024.11.13 |
[JAVA] Calendar 클래스 / 오늘 날짜 구하기 / 이번 달의 첫번째 날 / 이번 달의 마지막 날 / 현재로부터 과거까지 역순 데이트 리스트 출력 (0) | 2023.05.10 |
[JAVA] POJO (Plain Old Java Object) (0) | 2023.03.07 |
[JAVA] 콤마로 구분되어 저장된 String 데이터 List화 하기 (0) | 2023.02.21 |