개발노트

7. [ASP .NET Core] HttpClient 올바르게 사용하기 본문

서버 개발/ASP .NET Core

7. [ASP .NET Core] HttpClient 올바르게 사용하기

mroh1226 2025. 2. 3. 02:21
반응형

.NET Framework 에서 사용했다면 아래 보통 아래와 같이 HttpClient 인스턴스를 생성하고 요청, 응답을 보내고 받는 방법을 사용했을 거야

HttpClient client = new HttpClient();

 

그냥 이렇게 사용했다면 어떤 것들이 문제되었을까?

MicroSoft에서는 기존 HttpClient의 어떤 문제들을 인지하였고 이것들을 고치기 위해 어떤 방안들을 제시했을지 같이 알아보자

.NET Core 2.1부터는 아래와 같이 사용할 수 있을거야


문제점 1. 소켓 고갈

- 소켓 고갈이 생기는 원인은 뭘까?
커넥션  Pool을 이용하지않고 새로운 인스턴스를 생성하기 때문에, 항상 새로운 연결을 생성하고 해제해

 

  • HttpClient를 매번 new HttpClient()로 생성하면, 내부적으로 HttpClientHandler도 같이 생성됨.
  • HttpClientHandler는 새로운 TCP 연결을 만들고, 요청을 처리한 후 해당 연결을 닫음.

 

따라서, 매번 새로운 연결을 맺고 해제하여 새로운 포트를 사용하고 이는 소켓 고갈문제가 발생시켜


- 왜? 해제 한다면 연결을 새로 생성하더라도 그 포트를 다시 사용할 수 있는 것 아니야?

응 아니야, 이유는

Dispose로 해제 하더라도 연결에 사용된 포트가 TIME_WAIT 상태로 넘어가기 때문에 해제된 포트를 바로 사용할 수 없어서 사용하지않고 있는 새로운 또 다른 포트를 사용해, 이 때문에 사용할 포트가 점점 줄어들고, 포트 고갈 문제가 생기는거지, 운영체제마다 동적으로 사용할 수 있는 포트의 수도 정해져있어 (윈도우10, 11 각각 달라)

  • 연결이 닫혀도 운영체제(OS) 차원에서 바로 정리되지 않음.
  • 연결이 닫힌 후에도 일정 시간 동안 TIME_WAIT 상태로 유지됨.
  • 이 때문에 사용 가능한 포트가 빠르게 소진되면서 "소켓 고갈(Socket Exhaustion)" 문제가 발생함.
  • OS에서 사용할 수 있는 동적 포트의 개수는 제한되어 있음(Windows 10/11, Linux마다 다름).

 

문제점 1. 소켓 고갈 해결 방법

- 그렇다면 소켓 고갈을 해결방법은 뭐야?

HttpClient를 static으로 선언하여 사용하거나, 싱글톤으로 사용하여, 하나의 인스턴스만 사용하게 하면 해결 돼

builder.Services.AddSigleton((serviceProvider) => new HttpClient())

 


문제점 2. DNS 업데이트 불가

하지만, 여기서 아직 해결되지않은 문제점이 있어 그것은 바로, DNS가 변경되었을 때 이 변경사항이 업데이트가 안된다라는 것이지

 

- 왜? DNS 변경을 감지하지 못해?
왜냐하면 하나의 인스턴스를 사용하게 되면 기존에 적용된 DNS를 가진 HttpClient 인스턴스만 사용하게 되니까,
App을 다시 시작하거나 변경할 로직을 따로 주지않는 이상 DNS가 업데이트 되지않아

 

  • 앱이 실행될 때 HttpClient는 DNS 조회를 수행함.
  • 이후 동일한 HttpClient 인스턴스를 계속 사용하면서 처음 조회된 DNS 정보를 유지함.
  • 만약 백엔드 서버의 IP가 변경되었다면, 앱은 여전히 기존 IP를 향해 요청을 보내게 됨.
  • 결국 DNS 업데이트가 반영되지 않아 장애가 발생할 가능성이 있음.

 

문제점 2. DNS 업데이트 하기

- 그렇다면, DNS 업데이트를 주기적으로 해줄 수 있는 방법이 뭐야?

App이 실행되는 동안 매번 동일한 HttpClient 인스턴스와 속성 값들을 계속 사용되지않게 하면 돼,

즉, 시한부처럼 이 인스턴스의 커넥션에 살아 있을 수 있는 시간을 부여하는 거지, 지정된 연결 지속시간이 땡하고 되면 이 연결은 끊어게되고 그 다음 요청이 오면 다시 새롭게 연결을 맺고 새로운 커넥션 정보를 다시 받아올거야

이걸 할 수 있는 방법은 SocketsHttpHandler을 사용하는 거야, 기존에 사용했던 HttpClientHandler에는 이 커넥션 유지시간을 부여할 수 있는 옵션이 없거든

HttpClient의 핸들러로 SocketsHttpHandler를 지정하고 SocketsHttpHandler 옵션 중 하나인 PooledConnectionLifetime으로 커넥션 유지 시간을 부여할 수 있어

builder.Services.AddSingleton((serviceProvider) => HttpClient(
	new SocketsHttpHandler{
    		PooledConnectionLifetime = TimeSpan.FromMinutes(5) // 이 커넥션 지속시간은 5분 줄께
            }
));

위처럼 하면 5분마다 DNS가 업데이트 되는 거지

 

  • PooledConnectionLifetime을 설정하면 지정된 시간이 지나면 기존 연결을 닫고 새로운 연결을 생성함.
  • 즉, DNS가 변경되더라도 일정 주기마다 새롭게 조회하게 됨.
  • 하지만 이 방법도 싱글톤 패턴을 유지해야 하는 불편함이 있음.

 


더 좋은 방법은 없어?

싱글톤,Static 없이 사용할 수 있는 방법이 있어, 바로 HttpClientFactory 를 사용하는 것이지

HttpClientFactory 사용하기

HttpClientFactory의 역할은 HttpClient 풀(Pool)을 만들어주는 역할을 해

 

즉, 싱글톤처럼 하나의 인스턴스를 고정적으로 유지할 필요 없이, 적절히 풀에서 재사용되는 거지
이는 HttpClient 인스턴스 숫자를 관리해주고, 재사용될 수 있도록 도와주는 좀 더 나은 방식이지

 

우리는 HttpClient가 필요할 때 그냥 HttpClientFactory.CreateClient() 메서드를 호출해서 풀에 있는 HttpClient 를 사용하기만 하면되는거야

    • HttpClient 인스턴스를 자동으로 관리
      • 싱글톤처럼 하나의 인스턴스를 고정적으로 유지할 필요 없이, 적절히 풀에서 재사용됨.
    • DNS 변경 자동 반영
      • SocketsHttpHandler가 내부적으로 관리되므로, PooledConnectionLifetime을 적절히 조정하여 DNS가 변경되면 자동으로 업데이트됨.
    • 각각의 HttpClient를 개별 구성 가능
      • 특정 API 호출에 대해 개별적인 HttpClient 설정이 가능함.
// 서비스 등록
builder.Services.AddHttpClient(); 

// 사용 예시
public class MyService {
    private readonly HttpClient _httpClient;

    public MyService(IHttpClientFactory httpClientFactory) {
        _httpClient = httpClientFactory.CreateClient(); // 풀에서 HttpClient를 가져옴
    }

    public async Task<string> GetDataAsync() {
        var response = await _httpClient.GetAsync("https://example.com");
        return await response.Content.ReadAsStringAsync();
    }
}
  • IHttpClientFactory를 주입받아서 CreateClient()를 호출하면 풀에서 HttpClient를 가져옴.
  • 따라서, 불필요한 인스턴스 생성 없이 재사용되며, DNS 변경도 자동으로 반영됨.

이렇게 좋은 대안인  HttpClientFactory를 사용할 때도 주의할 점이 있어 바로 쿠키를 사용하는 경우야...

HttpClientFactory를 사용할 때 쿠키(CookieContainer) 를 사용하는 경우 다음과 같이 예외적인 상황이 발생할 수 있어.

HttpClientFactory 에서 쿠키(CookieContainer) 사용 시 문제점

HttpClientFactory는 내부적으로 HttpMessageHandler 풀(Pool) 을 관리하는데, HttpClient를 요청할 때마다 새로운 인스턴스를 반환하지만, 내부 핸들러는 재사용될 수 있어.

이게 왜 문제냐면?

  1. 쿠키를 저장하는 CookieContainer는 HttpMessageHandler에 묶여 있음
    • HttpClientFactory.CreateClient() 로 HttpClient를 가져올 때, 새로운 인스턴스를 받더라도 이전 요청에서 사용된 핸들러(HttpMessageHandler)를 재사용할 수 있어.
    • 따라서 쿠키가 의도치 않게 공유될 가능성이 있음.
  2. 쿠키를 분리해서 관리하고 싶어도 기본 HttpClientFactory에서는 불가능
    • HttpClientFactory로 생성한 HttpClient는 내부적으로 SocketsHttpHandler를 사용하지만, 이 핸들러는 쿠키를 따로 저장하지 않음.
    • 즉, CookieContainer를 명시적으로 설정하려면 기본 HttpClientFactory가 아니라 커스텀 핸들러를 사용한 HttpClientFactory를 설정해야 함.

- 쿠키를 개별적으로 관리하는 방법은 뭐가 있어?

HttpClientFactory에서 쿠키 컨테이너 사용하기

쿠키를 개별적으로 관리하려면 HttpClientFactory에서 HttpMessageHandler를 커스터마이징해서 사용해야 해.
아래처럼 IHttpClientFactory에 등록할 때 SocketsHttpHandler에 CookieContainer를 직접 설정할 수 있어.

services.AddHttpClient("WithCookies")
    .ConfigurePrimaryHttpMessageHandler(() => 
    {
        return new SocketsHttpHandler
        {
            UseCookies = true,
            CookieContainer = new CookieContainer(),
            PooledConnectionLifetime = TimeSpan.FromMinutes(5) // 5분마다 새로운 연결
        };
    });

이렇게 하면 "WithCookies"라는 이름의 HttpClient를 요청할 때마다 쿠키를 저장할 CookieContainer가 유지됨.

 

사용할 때는 아래처럼 하면 돼

var client = httpClientFactory.CreateClient("WithCookies");

 

  • HttpClientFactory를 사용하면 DNS 업데이트가 가능하고, HttpClient 풀을 관리해 성능도 최적화할 수 있음
  • 하지만 쿠키(CookieContainer)가 필요하다면 기본 HttpClientFactory를 그대로 쓰면 안 되고, ConfigurePrimaryHttpMessageHandler()를 사용해 커스텀 핸들러를 설정해야 함
  • 쿠키를 개별적으로 관리하고 싶다면 요청마다 CookieContainer를 따로 생성해서 사용해야 함

 

반응형
Comments