HttpClient 사용 지침

System.Net.Http.HttpClient 클래스는 URI로 식별되는 리소스에서 HTTP 요청을 보내고 HTTP 응답을 받습니다. HttpClient 인스턴스는 해당 인스턴스에서 실행되는 모든 요청에 적용되는 설정의 컬렉션이며, 각 인스턴스는 자체 연결 풀을 사용하여 요청을 다른 인스턴스와 격리합니다. .NET Core 2.1부터 SocketsHttpHandler 클래스는 구현을 제공하여 모든 플랫폼에서 일관된 동작이 가능합니다.

DNS 동작

HttpClient는 연결을 설정할 때 DNS 항목만 확인합니다. DNS 서버에서 지정한 TTL(Time to Live) 기간을 추적하지 않습니다. 일부 시나리오에서 발생할 수 있듯이 DNS 항목이 정기적으로 변경되는 경우 클라이언트는 이러한 업데이트를 적용하지 않습니다. 이 문제를 해결하려면 연결을 바꿀 때 DNS 조회가 반복되도록 PooledConnectionLifetime 속성을 설정하여 연결 수명을 제한할 수 있습니다. 다음과 같은 예제를 참조하세요.

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(15) // Recreate every 15 minutes
};
var sharedClient = new HttpClient(handler);

위의 HttpClient는 15분 동안 연결을 다시 사용하도록 구성되었습니다. PooledConnectionLifetime에서 지정한 timespan이 경과되고 연결이 마지막으로 연결된 요청(있는 경우)을 완료하면 이 연결이 닫힙니다. 큐에서 대기 중인 요청이 있는 경우 필요에 따라 새 연결이 만들어집니다.

15분 간격은 설명 목적으로 임의로 선택되었습니다. DNS 또는 기타 네트워크 변경의 예상 빈도에 따라 값을 선택해야 합니다.

풀링된 연결

HttpClient에 대한 연결 풀은 기본 SocketsHttpHandler에 연결됩니다. HttpClient 인스턴스가 삭제되면 풀 내의 기존 연결이 모두 삭제됩니다. 나중에 동일한 서버에 요청을 보내는 경우 새 연결을 다시 만들어야 합니다. 따라서 불필요한 연결 만들기로 인한 성능 저하가 발생합니다. 또한 TCP 포트는 연결 종료 직후에 해제되지 않습니다. (자세한 내용은 RFC 9293의 TCP TIME-WAIT를 참조하세요.) 요청 속도가 높으면 사용 가능한 포트의 운영 체제 제한이 소진될 수 있습니다. 포트 소진 문제를 방지하려면 HttpClient 인스턴스를 가능한 한 많은 HTTP 요청에 다시 사용하는 것이 좋습니다.

수명 관리 측면에서 권장되는 HttpClient 사용을 요약하려면 수명이 긴 클라이언트를 사용하고 PooledConnectionLifetime(.NET Core 및 .NET 5 이상)을 설정하거나, IHttpClientFactory에서 만든 수명이 짧은 클라이언트를 사용해야 합니다.

  • .NET Core 및 .NET 5 이상:

    • 예상된 DNS 변경 내용에 따라 PooledConnectionLifetime을 원하는 간격(예: 2분)으로 설정하여 static 또는 싱글톤 HttpClient 인스턴스를 사용합니다. 이렇게 하면 IHttpClientFactory의 오버헤드를 추가하지 않고 포트 소진 및 DNS 변경 문제가 모두 해결됩니다. 처리기를 모의해야 하는 경우 별도로 등록할 수 있습니다.

    제한된 수의 HttpClient 인스턴스만 사용하는 경우 이는 허용 가능한 전략이기도 합니다. 중요한 것은 이들이 요청마다 생성 및 삭제되지 않는다는 것입니다. 각 요청이 연결 풀을 포함하기 때문입니다. 여러 프록시가 있는 시나리오 또는 쿠키 처리를 완전히 사용하지 않도록 설정하지 않고 쿠키 컨테이너를 분리하려는 경우에는 둘 이상의 인스턴스를 사용해야 합니다.

    • IHttpClientFactory를 사용하면 다양한 사용 사례에 대해 다르게 구성된 클라이언트가 여러 개 있을 수 있습니다. 그러나 팩터리에서 만든 클라이언트는 수명이 짧아야 하며 일단 만들어진 클라이언트는 팩터리에서 더 이상 제어할 수 없습니다.

      팩터리는 HttpMessageHandler 인스턴스를 풀링하며, 팩터리에서 새 HttpClient 인스턴스를 만들 때 풀에서 수명이 만료되지 않은 처리기를 다시 사용할 수 있습니다. 이 재사용은 소켓 소진 문제를 방지합니다.

      IHttpClientFactory가 제공하는 구성 가능성이 필요한 경우 형식화된 클라이언트 접근 방식을 사용하는 것이 좋습니다.

  • .NET Framework에서는 IHttpClientFactory를 사용하여 HttpClient 인스턴스를 관리합니다. 팩터리를 사용하지 않고 각 요청에 대한 새 클라이언트 인스턴스를 직접 만드는 경우 사용 가능한 포트를 소진할 수 있습니다.

    앱에 쿠키가 필요한 경우 자동 쿠키 처리를 사용하지 않도록 설정하거나 IHttpClientFactory을 사용하지 않는 것이 좋습니다. HttpMessageHandler 인스턴스를 풀링하면 CookieContainer 개체가 공유됩니다. 예상치 못한 CookieContainer 개체 공유로 잘못된 코드가 발생하는 경우가 많습니다.

IHttpClientFactory을 사용한 HttpClient 수명 관리에 대한 자세한 내용은 IHttpClientFactory 지침을 참조하세요.

정적 클라이언트를 사용하는 복원력

다음 패턴을 활용하여 여러 복원력 파이프라인을 사용하도록 static 또는 싱글톤 클라이언트를 구성할 수 있습니다.

using System;
using System.Net.Http;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Http.Resilience;
using Polly;

var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(new HttpRetryStrategyOptions
    {
        BackoffType = DelayBackoffType.Exponential,
        MaxRetryAttempts = 3
    })
    .Build();

var socketHandler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(15)
};
var resilienceHandler = new ResilienceHandler(retryPipeline)
{
    InnerHandler = socketHandler,
};

var httpClient = new HttpClient(resilienceHandler);

앞의 코드가 하는 역할은 다음과 같습니다.

  • Microsoft.Extensions.Http.Resilience NuGet 패키지를 활용합니다.
  • 각 시도에서 기하급수적으로 백오프 지연 간격을 가져오는 재시도 파이프라인으로 구성된 일시적인 HTTP 오류 처리기를 지정합니다.
  • socketHandler에 대한 풀된 연결 수명(15분)을 정의합니다.
  • 재시도 논리를 사용하여 socketHandlerresilienceHandler에 전달합니다.
  • resilienceHandler가 지정된 HttpClient를 인스턴스화합니다.

Important

Microsoft.Extensions.Http.Resilience 라이브러리는 현재 실험용으로 표시되어 있으며 향후 변경될 수 있습니다.

참고 항목