Używanie elementu IHttpClientFactory do implementowania odpornych na błędy żądań HTTP

Napiwek

Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

IHttpClientFactory jest kontraktem implementowanym przez DefaultHttpClientFactoryfabrykę z opiniami dostępną od wersji .NET Core 2.1 na potrzeby tworzenia HttpClient wystąpień do użycia w aplikacjach.

Problemy z oryginalną klasą HttpClient dostępną na platformie .NET

Oryginalna i dobrze znana HttpClient klasa może być łatwo używana, ale w niektórych przypadkach nie jest ona prawidłowo używana przez wielu deweloperów.

Mimo że ta klasa implementuje IDisposablemetodę , deklarując i tworząc wystąpienie w using instrukcji, nie jest preferowana, ponieważ gdy HttpClient obiekt zostanie usunięty, bazowe gniazdo nie zostanie natychmiast zwolnione, co może prowadzić do problemu z wyczerpaniem gniazd. Aby uzyskać więcej informacji na temat tego problemu, zobacz wpis w blogu Używasz błędu HttpClient i destabilizuje oprogramowanie.

HttpClient W związku z tym ma zostać utworzone wystąpienie raz i ponownie użyte przez cały czas stosowania. HttpClient Utworzenie wystąpienia klasy dla każdego żądania spowoduje wyczerpanie liczby gniazd dostępnych pod dużym obciążeniem. Ten problem spowoduje SocketException błędy. Możliwe podejścia do rozwiązania tego problemu są oparte na tworzeniu HttpClient obiektu jako pojedynczego lub statycznego, jak wyjaśniono w tym artykule firmy Microsoft na temat użycia klienta HttpClient. Może to być dobre rozwiązanie dla krótkotrwałych aplikacji konsolowych lub podobnych, które działają kilka razy dziennie.

Innym problemem, który deweloperzy napotkają, jest użycie udostępnionego HttpClient wystąpienia w długotrwałych procesach. W sytuacji, gdy obiekt HttpClient jest tworzone jako pojedynczy lub statyczny obiekt, nie może obsłużyć zmian DNS zgodnie z opisem w tym problemie repozytorium GitHub dotnet/runtime.

Jednak problem nie jest naprawdę związany z HttpClient per se, ale z domyślnym konstruktorem httpClient, ponieważ tworzy nowe konkretne wystąpienie HttpMessageHandlerprogramu , czyli to, który ma gniazda wyczerpania i problemy z zmianami DNS wymienione powyżej.

Aby rozwiązać wymienione powyżej problemy i umożliwić HttpClient zarządzanie wystąpieniami, platforma .NET Core 2.1 wprowadziła dwa podejścia— jedna z nich to IHttpClientFactory. Jest to interfejs używany do konfigurowania i tworzenia HttpClient wystąpień w aplikacji za pomocą wstrzykiwania zależności (DI). Udostępnia również rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly, aby móc korzystać z delegowania programów obsługi w programie HttpClient.

Alternatywą jest użycie z skonfigurowanym PooledConnectionLifetimeprogramem SocketsHttpHandler . Takie podejście jest stosowane do długotrwałych static wystąpień lub pojedynczych HttpClient wystąpień. Aby dowiedzieć się więcej na temat różnych strategii, zobacz Wskazówki dotyczące klienta HttpClient dla platformy .NET.

Polly to biblioteka obsługująca błędy przejściowe, która ułatwia deweloperom dodawanie odporności do swoich aplikacji przy użyciu wstępnie zdefiniowanych zasad w sposób płynny i bezpieczny wątkowo.

Zalety korzystania z elementu IHttpClientFactory

Bieżąca implementacja programu IHttpClientFactory, która również implementuje IHttpMessageHandlerFactoryprogram , oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania obiektów logicznych HttpClient . Można na przykład skonfigurować klienta (agenta usługi), który jest wstępnie skonfigurowany do uzyskiwania dostępu do określonej mikrousługi.
  • Kodyfikują koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w HttpClient oprogramowaniu pośredniczącym opartym na usłudze Polly i implementowanie oprogramowania pośredniczącego opartego na usłudze Polly w celu skorzystania z zasad Polly w celu zapewnienia odporności.
  • HttpClient Ma już koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. Można zarejestrować klientów HTTP w fabryce i użyć programu obsługi Polly do używania zasad Polly dla ponawiania prób, obwodów i tak dalej.
  • Zarządzaj okresem istnienia, HttpMessageHandler aby uniknąć wymienionych problemów/problemów, które mogą wystąpić podczas samodzielnego zarządzania HttpClient okresami istnienia.

Napiwek

HttpClient Wystąpienia wprowadzone przez di można bezpiecznie usunąć, ponieważ skojarzony z nim HttpMessageHandler jest zarządzany przez fabrykę. Iniekcyjne HttpClient wystąpienia są przejściowe z perspektywy di, podczas gdy HttpMessageHandler wystąpienia mogą być traktowane jako Zakres. HttpMessageHandler wystąpienia mają własne zakresy di, oddzielone od zakresów aplikacji (na przykład ASP.NET zakresów żądań przychodzących). Aby uzyskać więcej informacji, zobacz Using HttpClientFactory in .NET (Używanie elementu HttpClientFactory na platformie .NET).

Uwaga

Implementacja IHttpClientFactory (DefaultHttpClientFactory) jest ściśle powiązana z implementacją di w Microsoft.Extensions.DependencyInjection pakiecie NuGet. Jeśli musisz używać HttpClient bez di lub z innymi implementacjami di, rozważ użycie pojedynczego static elementu HttpClient lub z konfiguracją PooledConnectionLifetime . Aby uzyskać więcej informacji, zobacz Wskazówki dotyczące klienta HttpClient dla platformy .NET.

Wiele sposobów używania elementu IHttpClientFactory

Istnieje kilka sposobów użycia IHttpClientFactory w aplikacji:

  • Podstawowy sposób użycia
  • Używanie nazwanych klientów
  • Korzystanie z typowanych klientów
  • Korzystanie z wygenerowanych klientów

Ze względu na zwięzłość te wskazówki pokazują najbardziej ustrukturyzowany sposób korzystania z IHttpClientFactoryprogramu , który polega na użyciu typu klientów (wzorzec agenta usługi). Jednak wszystkie opcje są udokumentowane i są obecnie wymienione w tym artykule obejmującym IHttpClientFactory użycie.

Uwaga

Jeśli aplikacja wymaga plików cookie, lepszym rozwiązaniem może być unikanie korzystania z IHttpClientFactory aplikacji. Aby uzyskać alternatywne sposoby zarządzania klientami, zobacz Wytyczne dotyczące korzystania z klientów HTTP

Jak używać typowanych klientów z IHttpClientFactory

Co to jest "Typd Client"? Jest to tylko HttpClient element wstępnie skonfigurowany do określonego użycia. Ta konfiguracja może zawierać określone wartości, takie jak serwer podstawowy, nagłówki HTTP lub przekroczenia limitu czasu.

Na poniższym diagramie przedstawiono sposób użycia klientów typu z usługą IHttpClientFactory:

Diagram showing how typed clients are used with IHttpClientFactory.

Rysunek 8–4. Używanie z IHttpClientFactory typowymi klasami klienta.

Na powyższej ilustracji ClientService obiekt (używany przez kontroler lub kod klienta) używa HttpClient elementu utworzonego przez zarejestrowany IHttpClientFactoryobiekt . Ta fabryka przypisuje element HttpMessageHandler z puli do obiektu HttpClient. Można HttpClient je skonfigurować przy użyciu zasad polly podczas rejestrowania IHttpClientFactory w kontenerze DI za pomocą metody AddHttpClientrozszerzenia .

Aby skonfigurować powyższą strukturę, dodaj IHttpClientFactory aplikację, instalując Microsoft.Extensions.Http pakiet NuGet zawierający metodę AddHttpClient rozszerzenia dla IServiceCollectionprogramu . Ta metoda rozszerzenia rejestruje klasę wewnętrzną DefaultHttpClientFactory , która ma być używana jako pojedynczy element dla interfejsu IHttpClientFactory. Definiuje on konfigurację przejściową dla elementu HttpMessageHandlerBuilder. Ta procedura obsługi komunikatów (HttpMessageHandler obiekt), pobrana z puli, jest używana przez HttpClient obiekt zwrócony z fabryki.

W następnym fragmencie kodu można zobaczyć, jak AddHttpClient() można użyć do rejestrowania klientów typu (agentów usługi), które muszą używać programu 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>();

Zarejestrowanie usług klienckich, jak pokazano w poprzednim fragmencie kodu, powoduje utworzenie standardu DefaultClientFactoryHttpClient dla każdej usługi. Typowany klient jest zarejestrowany jako przejściowy w kontenerze DI. W poprzednim kodzie rejestruje usługę CatalogService, BasketService, OrderingService jako usługi przejściowe, AddHttpClient() aby można je było wstrzyknąć i używać bezpośrednio bez konieczności dodatkowych rejestracji.

Możesz również dodać konfigurację specyficzną dla wystąpienia w rejestracji, aby na przykład skonfigurować adres podstawowy i dodać pewne zasady odporności, jak pokazano poniżej:

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

W następnym przykładzie zobaczysz konfigurację jednej z powyższych zasad:

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

Więcej szczegółów na temat korzystania z usługi Polly można znaleźć w artykule Next (Dalej).

Okresy istnienia klienta HttpClient

Za każdym razem, gdy otrzymujesz HttpClient obiekt z IHttpClientFactoryobiektu , zwracane jest nowe wystąpienie. Jednak każdy HttpClient z nich używa puli HttpMessageHandler i ponownego użycia przez IHttpClientFactory element , aby zmniejszyć zużycie zasobów, o ile HttpMessageHandlerokres istnienia nie wygasł.

Buforowanie programów obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP; utworzenie większej liczby procedur obsługi, niż jest to konieczne, może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany DNS.

Obiekty HttpMessageHandler w puli mają okres istnienia, przez który HttpMessageHandler można ponownie użyć wystąpienia w puli. Wartość domyślna to dwie minuty, ale można ją zastąpić na typizowanego klienta. Aby go zastąpić, wywołaj SetHandlerLifetime()IHttpClientBuilder element zwrócony podczas tworzenia klienta, jak pokazano w poniższym kodzie:

//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));

Każdy klient typu może mieć własną skonfigurowaną wartość okresu istnienia programu obsługi. Ustaw okres istnienia, aby InfiniteTimeSpan wyłączyć wygaśnięcie programu obsługi.

Zaimplementuj klasy klienta typu, które używają wstrzykniętego i skonfigurowanego klienta HttpClient

W poprzednim kroku należy zdefiniować klasy klienta typu, takie jak klasy w przykładowym kodzie, takie jak "BasketService", "CatalogService", "OrderingService", itp. — typdowany klient to klasa, która akceptuje HttpClient obiekt (wstrzykiwany za pośrednictwem konstruktora) i używa go do wywoływania zdalnej usługi HTTP. Na przykład:

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;
    }
}

Typowany klient (CatalogService w przykładzie) jest aktywowany przez di (wstrzykiwanie zależności), co oznacza, że może zaakceptować dowolną zarejestrowaną usługę w konstruktorze, oprócz HttpClient.

Klient typu jest w rzeczywistości obiektem przejściowym, co oznacza, że nowe wystąpienie jest tworzone za każdym razem, gdy jest potrzebny. Otrzymuje nowe HttpClient wystąpienie za każdym razem, gdy jest tworzone. HttpMessageHandler Jednak obiekty w puli są obiektami, które są ponownie używane przez wiele HttpClient wystąpień.

Używanie klas klienta typu

Na koniec po zaimplementowaniu klas wpisanych można je zarejestrować i skonfigurować za pomocą polecenia AddHttpClient(). Następnie można używać ich wszędzie tam, gdzie usługi są wstrzykiwane przez di, na przykład w kodzie strony Razor lub kontrolerze aplikacji internetowej MVC, pokazanym w poniższym kodzie z eShopOnContainers:

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
        }

        }
}

Do tego momentu powyższy fragment kodu pokazuje tylko przykład wykonywania regularnych żądań HTTP. Jednak "magia" jest dostępna w poniższych sekcjach, w których pokazano, jak wszystkie żądania HTTP wysyłane przez HttpClient program mogą mieć odporne zasady, takie jak ponawianie prób z wykładniczym wycofywaniem, wyłącznikami, funkcjami zabezpieczeń przy użyciu tokenów uwierzytelniania, a nawet innymi funkcjami niestandardowymi. Wszystkie te czynności można wykonać tylko przez dodanie zasad i delegowanie programów obsługi do zarejestrowanych klientów wpisanych.

Dodatkowe zasoby