다음을 통해 공유


IHttpClientFactory를 사용하여 복원력 있는 HTTP 요청 구현

이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공되는 컨테이너화된 .NET 애플리케이션용 .NET 마이크로 서비스 아키텍처인 eBook에서 발췌한 내용입니다.

컨테이너화된 .NET 애플리케이션 eBook 표지 썸네일을 위한 .NET 마이크로서비스 아키텍처

IHttpClientFactory은(는) DefaultHttpClientFactory이 구현한, .NET Core 2.1부터 사용 가능한 의견이 일부 반영된(관습 기반의) 팩터리로, 애플리케이션에서 사용할 HttpClient 인스턴스 생성을 위한 계약입니다.

.NET에서 사용할 수 있는 원래 HttpClient 클래스와 관련된 문제

원래의 잘 알려진 HttpClient 클래스는 쉽게 사용할 수 있지만, 경우에 따라 많은 개발자가 제대로 사용하지 않습니다.

이 클래스는 IDisposable구현하지만 HttpClient 개체가 삭제될 때 기본 소켓이 즉시 해제되지 않아 소켓 고갈 문제가 발생할 수 있으므로 using 문 내에서 선언하고 인스턴스화하는 것이 바람직하지 않습니다. 이 문제에 대한 자세한 내용은 HttpClient를 잘못 사용하고 있으며 소프트웨어 불안정하게 만드는블로그 게시물을 참조하세요.

따라서 HttpClient 애플리케이션의 수명 동안 한 번 인스턴스화되고 다시 사용되도록 하기 위한 것입니다. 모든 요청에 대해 HttpClient 클래스를 인스턴스화하면 부하가 많은 상태에서 사용 가능한 소켓 수가 소진됩니다. 이 문제로 인해 SocketException 오류가 발생합니다. 이 문제를 해결할 수 있는 방법은 httpClient 사용량 대한 이Microsoft 문서에서 설명한 대로 HttpClient 개체를 싱글톤 또는 정적 개체로 만드는 방법을 기반으로 합니다. 이는 하루에 몇 번 실행되는 수명이 짧은 콘솔 앱 또는 이와 유사한 솔루션이 될 수 있습니다.

개발자가 직면하는 또 다른 문제는 장기 실행 프로세스에서 HttpClient 공유 인스턴스를 사용하는 경우입니다. HttpClient가 싱글톤 또는 정적 개체로 인스턴스화되는 경우 dotnet/runtime GitHub 리포지토리의 문제에 설명된 대로 DNS 변경 내용을 처리하지 못합니다.

그러나 문제는 실제로 HttpClient 문제가 아니라 HttpClient 대한기본 생성자와 함께 소켓 고갈 DNS 변경 문제가 있는 HttpMessageHandler새 구체적인 인스턴스를 만들기 때문입니다.

위에서 언급한 문제를 해결하고 HttpClient 인스턴스를 관리하기 위해 .NET Core 2.1에는 두 가지 방법이 도입되었으며, 그 중 하나는 IHttpClientFactory. DI(종속성 주입)를 통해 앱에서 HttpClient 인스턴스를 구성하고 만드는 데 사용되는 인터페이스입니다. 또한 HttpClient에서 위임 처리기를 활용할 수 있도록 Polly 기반 미들웨어에 대한 확장을 제공합니다.

대안으로 구성된 PooledConnectionLifetime과 함께 SocketsHttpHandler를 사용합니다. 이 방법은 수명이 긴 static 또는 싱글톤 HttpClient 인스턴스에 적용됩니다. 다양한 전략에 대한 자세한 내용은 .NET 대한HttpClient 지침을 참조하세요.

Polly 개발자가 유창하고 스레드로부터 안전한 방식으로 미리 정의된 정책을 사용하여 애플리케이션에 복원력을 추가하는 데 도움이 되는 일시적인 오류 처리 라이브러리입니다.

IHttpClientFactory 사용의 이점

IHttpClientFactory의 현재 구현은 IHttpMessageHandlerFactory을 구현하며 다음과 같은 이점을 제공합니다.

  • 논리적 HttpClient 개체의 이름을 지정하고 구성하기 위한 중앙 위치를 제공합니다. 예를 들어 특정 마이크로 서비스에 액세스하도록 미리 구성된 클라이언트(서비스 에이전트)를 구성할 수 있습니다.
  • HttpClient 처리기의 위임을 통해 나가는 미들웨어의 개념을 정립하고, Polly의 복원력 정책을 활용하기 위해 Polly 기반의 미들웨어를 구현합니다.
  • HttpClient 나가는 HTTP 요청에 대해 함께 연결할 수 있는 처리기를 위임하는 개념이 이미 있습니다. 팩터리에 HTTP 클라이언트를 등록할 수 있으며 Polly 처리기를 사용하여 다시 시도, CircuitBreakers 등에 Polly 정책을 사용할 수 있습니다.
  • HttpMessageHandler 수명을 관리하여 HttpClient 수명을 스스로 관리할 때 발생할 수 있는 문제를 방지합니다.

연결된 HttpMessageHandler 팩터리에서 관리되기 때문에 DI에 의해 주입된 HttpClient 인스턴스를 안전하게 삭제할 수 있습니다. 삽입된 HttpClient 인스턴스는 DI 관점에서 일시적인로 간주되는 반면, HttpMessageHandler 인스턴스는 범위가 지정된로 간주될 수 있습니다. HttpMessageHandler 인스턴스는 애플리케이션 범위(예: ASP.NET의 들어오는 요청 범위)와 별도로, 자체 DI 범위를 가지고 있습니다. 자세한 내용은 .NET에서 HttpClientFactory 사용을 참조하세요.

메모

IHttpClientFactory(DefaultHttpClientFactory)의 구현은 Microsoft.Extensions.DependencyInjection NuGet 패키지의 DI 구현과 긴밀하게 연결됩니다. DI나 다른 DI 구현체 없이 HttpClient을 사용해야 하는 경우, PooledConnectionLifetime가 설정된 static이나 싱글톤 HttpClient를 사용하는 것을 고려하세요. 자세한 내용은 .NET 대한HttpClient 지침을 참조하세요.

IHttpClientFactory를 사용하는 여러 가지 방법

애플리케이션에서 IHttpClientFactory 사용할 수 있는 몇 가지 방법이 있습니다.

  • 기본 사용량
  • 명명된 클라이언트 사용
  • 형식화된 클라이언트 사용
  • 생성된 클라이언트 사용

간결성을 위해 이 지침은 형식화된 클라이언트(서비스 에이전트 패턴)를 사용하는 IHttpClientFactory사용하는 가장 구조화된 방법을 보여 줍니다. 그러나 모든 옵션이 문서화되어 있으며 현재 이 문서에서는 IHttpClientFactory 사용다루고 있습니다.

메모

앱에 쿠키가 필요한 경우 앱에서 IHttpClientFactory 사용하지 않는 것이 더 나을 수 있습니다. 클라이언트를 관리하는 다른 방법은 HTTP 클라이언트 사용에 대한지침을 참조하세요.

IHttpClientFactory에서 형식화된 클라이언트를 사용하는 방법

그렇다면 "형식화된 클라이언트"란? 그것은 특정 용도로 미리 구성된 HttpClient일 뿐입니다. 이 구성에는 기본 서버, HTTP 헤더 또는 시간 제한과 같은 특정 값이 포함될 수 있습니다.

다음 다이어그램에서는 형식화된 클라이언트를 IHttpClientFactory사용하는 방법을 보여 줍니다.

IHttpClientFactory에서 형식화된 클라이언트를 사용하는 방법을 보여 주는 다이어그램

그림 8-4. 형식화된 클라이언트 클래스와 함께 IHttpClientFactory 사용.

위의 이미지에서, 등록된 IHttpClientFactory가 생성한 HttpClient을 컨트롤러나 클라이언트 코드에서 사용하는 ClientService이 사용합니다. 이 공장은 풀에서 HttpMessageHandlerHttpClient에 할당합니다. DI 컨테이너에 IHttpClientFactory을 등록할 때, 확장 메서드 AddHttpClient를 사용하여 Polly의 정책으로 HttpClient을 구성할 수 있습니다.

위의 구조를 구성하려면 IServiceCollectionAddHttpClient 확장 메서드를 포함하는 Microsoft.Extensions.Http NuGet 패키지를 설치하여 애플리케이션에 IHttpClientFactory 추가합니다. 이 확장 메서드는 인터페이스 IHttpClientFactory싱글톤으로 사용할 내부 DefaultHttpClientFactory 클래스를 등록합니다. HttpMessageHandlerBuilder대한 임시 구성을 정의합니다. 풀에서 가져온 이 메시지 처리기(HttpMessageHandler 개체)는 팩터리에서 반환된 HttpClient에 의해 사용됩니다.

다음 코드 조각에서는 AddHttpClient() 사용하여 HttpClient사용해야 하는 형식화된 클라이언트(서비스 에이전트)를 등록하는 방법을 확인할 수 있습니다.

// Program.cs
//Add http client services at ConfigureServices(IServiceCollection services)
builder.Services.AddHttpClient<ICatalogService, CatalogService>();
builder.Services.AddHttpClient<IBasketService, BasketService>();
builder.Services.AddHttpClient<IOrderingService, OrderingService>();

이전 코드 조각에 표시된 대로 클라이언트 서비스를 등록하면 DefaultClientFactory 각 서비스에 대한 표준 HttpClient 만듭니다. 형식화된 클라이언트는 DI 컨테이너에 임시로 등록됩니다. 앞의 코드에서 AddHttpClient()CatalogService, BasketService, OrderingService 임시 서비스로 등록하므로 추가 등록 없이 직접 삽입하고 사용할 수 있습니다.

예를 들어 등록에서 인스턴스별 구성을 추가하여 기본 주소를 구성하고 다음과 같이 일부 복원력 정책을 추가할 수도 있습니다.

builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

다음 예제에서는 위의 정책 중 하나의 구성을 볼 수 있습니다.

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

Polly 사용에 대한 자세한 내용은 다음 문서 에서 확인할 수 있습니다.

HttpClient 수명 주기

IHttpClientFactory HttpClient 개체를 가져올 때마다 새 인스턴스가 반환됩니다. 그러나 각 HttpClientIHttpClientFactory에 의해 풀링되고 다시 사용되는 HttpMessageHandler을 사용하여, HttpMessageHandler의 수명이 만료되지 않는 한 리소스 소비를 줄입니다.

각 처리기는 일반적으로 자체 기본 HTTP 연결을 관리하기 때문에 처리기의 풀링이 좋습니다. 필요한 것보다 더 많은 처리기를 만들면 연결이 지연될 수 있습니다. 또한 일부 처리기는 연결을 무기한으로 열어 두어 처리기가 DNS 변경에 반응하지 못하게 할 수 있습니다.

풀의 HttpMessageHandler 개체에는 풀의 HttpMessageHandler 인스턴스를 다시 사용할 수 있는 기간의 수명이 있습니다. 기본값은 2분이지만 형식화된 클라이언트별로 재정의할 수 있습니다. 재정의하려면 클라이언트를 만들 때 반환된 IHttpClientBuilder에서 SetHandlerLifetime()을 호출해야 합니다. 다음 코드에 나와 있는 것처럼 하십시오.

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
builder.Services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

각 형식화된 클라이언트에는 자체적으로 구성된 처리기 수명 값이 있을 수 있습니다. 처리기 만료를 비활성화하려면 수명을 InfiniteTimeSpan으로 설정하세요.

삽입되고 구성된 HttpClient를 사용하는 형식화된 클라이언트 클래스 구현

이전 단계에서는 샘플 코드의 클래스(예: 'BasketService', 'CatalogService', 'OrderingService' 등)와 같이 Typed Client 클래스를 정의해야 합니다. Typed Client는 HttpClient 개체를 허용하고(생성자를 통해 삽입) 이를 사용하여 일부 원격 HTTP 서비스를 호출하는 클래스입니다. 예를 들어:

public class CatalogService : ICatalogService
{
    private readonly HttpClient _httpClient;
    private readonly string _remoteServiceBaseUrl;

    public CatalogService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Catalog> GetCatalogItems(int page, int take,
                                               int? brand, int? type)
    {
        var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
                                                 page, take, brand, type);

        var responseString = await _httpClient.GetStringAsync(uri);

        var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
        return catalog;
    }
}

형식화된 클라이언트(예제의CatalogService)는 DI(종속성 주입)에 의해 활성화됩니다. 즉, HttpClient외에도 해당 생성자의 등록된 서비스를 수락할 수 있습니다.

Typed Client는 실제로 일시적인 개체입니다. 즉, 필요할 때마다 새 인스턴스가 만들어집니다. 생성될 때마다 새 HttpClient 인스턴스를 받습니다. 그러나 풀의 HttpMessageHandler 개체는 여러 HttpClient 인스턴스에서 다시 사용하는 개체입니다.

타입된 클라이언트 클래스 사용하기

마지막으로, 유형화된 클래스를 구현한 후에는, 그것들이 AddHttpClient()에 등록되고 구성되도록 할 수 있습니다. 그런 다음, eShopOnContainers의 아래 코드에 표시된 Razor 페이지 코드 또는 MVC 웹앱 컨트롤러와 같이 DI에서 서비스를 삽입하는 모든 위치에서 사용할 수 있습니다.

namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
    public class CatalogController : Controller
    {
        private ICatalogService _catalogSvc;

        public CatalogController(ICatalogService catalogSvc) =>
                                                           _catalogSvc = catalogSvc;

        public async Task<IActionResult> Index(int? BrandFilterApplied,
                                               int? TypesFilterApplied,
                                               int? page,
                                               [FromQuery]string errorMsg)
        {
            var itemsPage = 10;
            var catalog = await _catalogSvc.GetCatalogItems(page ?? 0,
                                                            itemsPage,
                                                            BrandFilterApplied,
                                                            TypesFilterApplied);
            //… Additional code
        }

        }
}

이 시점까지 위의 코드 조각은 일반 HTTP 요청을 수행하는 예제만 보여 줍니다. 그러나 '매직'은 다음 섹션에서 HttpClient이 수행한 모든 HTTP 요청에 대해 지수 백오프, 회로 차단기, 인증 토큰을 사용한 보안 기능, 또는 기타 다른 사용자 정의 기능을 통한 재시도와 같은 복원력 있는 정책을 구현할 수 있는 방법을 보여주는 데 있습니다. 또한 이러한 모든 작업은 정책을 추가하고 등록된 형식화된 클라이언트에 처리기를 위임하는 것만으로 수행할 수 있습니다.

추가 리소스