Udostępnij za pośrednictwem


Implementowanie odpornych żądań HTTP za pomocą elementu IHttpClientFactory

Napiwek

Ta treść jest fragmentem eBooka, architektura mikrousług .NET dla aplikacji .NET konteneryzowanych, dostępnego na .NET Docs lub jako bezpłatny plik PDF do pobrania, który można czytać w trybie offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail (Architektura mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET).

IHttpClientFactory jest kontraktem wdrożonym przez DefaultHttpClientFactory, fabrykę o sprecyzowanych opiniach, dostępną od wersji .NET Core 2.1, służącą do tworzenia wystąpień HttpClient do użycia w aplikacjach.

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

Oryginalna i dobrze znana klasa HttpClient 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 IDisposable, deklarowanie i instacjonowanie wewnątrz instrukcji using nie jest zalecane, ponieważ gdy obiekt typu HttpClient zostanie zniszczony, bazowe gniazdo nie zostaje natychmiast zwolnione, co może prowadzić do problemu wyczerpania gniazd. Aby uzyskać więcej informacji na temat tego problemu, zobacz wpis na blogu Używasz HttpClient w niewłaściwy sposób, co destabilizuje twoje oprogramowanie.

W związku z tym HttpClient ma zostać utworzone raz i ponownie użyte przez cały czas działania aplikacji. Utworzenie wystąpienia klasy HttpClient dla każdego żądania spowoduje wyczerpanie liczby gniazd dostępnych pod dużym obciążeniem. Ten problem spowoduje błędy typu SocketException. Możliwe podejścia do rozwiązania tego problemu opierają się na tworzeniu obiektu HttpClient jako singletona lub zmiennej statycznej, jak wyjaśniono w tym artykule Microsoft na temat użycia 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, jaki deweloperzy napotykają, jest użycie udostępnionego wystąpienia HttpClient 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 problem repozytorium dotnet/runtime w usłudze GitHub.

Jednak problem nie jest naprawdę związany z HttpClient na se, ale z domyślny konstruktor httpClient, ponieważ tworzy nowe konkretne wystąpienie HttpMessageHandler, który jest tym, który ma gniazd wyczerpania i problemy ze zmianami DNS wymienione powyżej.

Aby rozwiązać wymienione powyżej problemy i umożliwić zarządzanie wystąpieniami HttpClient, platforma .NET Core 2.1 wprowadziła dwa podejścia, z których jedna jest IHttpClientFactory. Jest to interfejs używany do konfigurowania i tworzenia instancji HttpClient w aplikacji poprzez wstrzykiwanie 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 SocketsHttpHandler ze skonfigurowanym PooledConnectionLifetime. Takie podejście jest stosowane do długotrwałych, static lub pojedynczych wystąpień HttpClient. Aby dowiedzieć się więcej na temat różnych strategii, zobacz Wskazówki dotyczące klienta HttpClient dla platformy .NET.

Polly to biblioteka obsługi błędów przejściowych, 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 dla wątków.

Zalety korzystania z narzędzia IHttpClientFactory

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

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania obiektów HttpClient logicznych. 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.
  • Skodyfikować koncepcję oprogramowania pośredniczącego dla wychodzących żądań poprzez delegowanie programów obsługi w HttpClient i implementację oprogramowania pośredniczącego opartego na Polly, aby wykorzystać zasady Polly dla zwiększenia 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 czasem istnienia HttpMessageHandler, aby uniknąć wymienionych problemów, które mogą wystąpić, gdy samodzielnie zarządzasz czasem istnienia HttpClient.

Napiwek

Instancje HttpClient wprowadzone przez DI można bezpiecznie zlikwidować, ponieważ skojarzony HttpMessageHandler jest zarządzany przez fabrykę. Iniekcyjne wystąpienia HttpClientprzejściowe z perspektywy di, a wystąpienia HttpMessageHandler można traktować jako o określonym zakresie. HttpMessageHandler wystąpienia mają własne zakresy DI, oddzielone od zakresów aplikacji (na przykład ASP.NET zakresu żądań przychodzących). Aby uzyskać więcej informacji, zobacz Using HttpClientFactory in .NET.

Notatka

Implementacja IHttpClientFactory (DefaultHttpClientFactory) jest ściśle powiązana z implementacją di w pakiecie NuGet Microsoft.Extensions.DependencyInjection. Jeśli musisz użyć HttpClient bez DI lub z innymi implementacjami DI, rozważ użycie static lub singletona HttpClient z odpowiednio skonfigurowanym PooledConnectionLifetime. Aby uzyskać więcej informacji, zobacz wytyczne HttpClient dotyczące platformy .NET.

Wiele sposobów używania elementu IHttpClientFactory

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

  • Podstawowe użycie
  • 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 użycia IHttpClientFactory, jakim jest zastosowanie klientów typowanych (wzorzec agenta usługi). Jednak wszystkie opcje są udokumentowane i są obecnie wymienione w tym artykule obejmującym zastosowanie IHttpClientFactory.

Nota

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

Jak używać typowanych klientów z IHttpClientFactory

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

Na poniższym diagramie przedstawiono sposób użycia Typowanych Klientów z użyciem IHttpClientFactory:

Diagram przedstawiający sposób użycia typowanych klientów z IHttpClientFactory.

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

Na powyższej ilustracji ClientService (używana przez kontroler lub kod klienta) używa HttpClient utworzonej przez zarejestrowaną IHttpClientFactory. Ta fabryka przypisuje HttpMessageHandler z puli do HttpClient. HttpClient można skonfigurować przy użyciu zasad Polly podczas rejestrowania IHttpClientFactory w kontenerze DI za pomocą metody rozszerzenia AddHttpClient.

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

W następnym fragmencie kodu można zobaczyć, jak można użyć AddHttpClient() do rejestrowania klientów typu (agentów usługi), którzy muszą używać 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, że DefaultClientFactory tworzy standardowe HttpClient dla każdej usługi. Typowany klient jest zarejestrowany jako przejściowy w kontenerze DI. W poprzednim kodzie AddHttpClient() rejestruje usługę CatalogService, BasketService, OrderingService jako usługi przejściowe, aby można je było wstrzykiwać i używać bezpośrednio bez konieczności dodatkowych rejestracji.

Możesz również dodać specyficzne ustawienia dla wystąpienia podczas rejestracji, aby na przykład skonfigurować adres bazowy i dodać reguły dotyczące 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.

Okresy istnienia klienta HttpClient

Za każdym razem, gdy otrzymujesz obiekt HttpClient z IHttpClientFactory, zwracane jest nowe wystąpienie. Jednak każdy HttpClient używa HttpMessageHandler, który jest współdzielony i ponownie używany przez IHttpClientFactory, w celu zmniejszenia zużycia zasobów, o ile okres istnienia HttpMessageHandlernie 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ą czas trwania równy okresowi, przez który można ponownie wykorzystać instancję HttpMessageHandler w puli. Wartość domyślna to dwie minuty, ale można ją zastąpić dla typizowanego klienta. Aby go zastąpić, wywołaj SetHandlerLifetime() w IHttpClientBuilder zwróconym 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 Typowany Klient może mieć własną skonfigurowaną wartość czasu życia obsługi. Ustaw okres istnienia na InfiniteTimeSpan, aby wyłączyć wygaśnięcie programu obsługi.

Zaimplementuj klasy Klienta Typed, które używają wstrzykniętego i skonfigurowanego HttpClient.

W poprzednim kroku należy zdefiniować typowane klasy klienta, takie jak klasy w przykładowym kodzie, na przykład "BasketService", "CatalogService", "OrderingService" itp. Typowana klasa klienta to klasa, która akceptuje obiekt HttpClient (wstrzykiwany przez konstruktor) 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 przyjąć dowolną zarejestrowaną usługę w konstruktorze, oprócz HttpClient.

Klient typu to faktycznie obiekt przejściowy, co oznacza, że nowa instancja jest tworzona za każdym razem, gdy jest potrzebna. Otrzymuje nowe wystąpienie HttpClient za każdym razem, gdy jest tworzone. Jednak obiekty HttpMessageHandler w puli są obiektami, które są ponownie używane przez wiele wystąpień HttpClient.

Użyj swoich klas klienta Type.

Na koniec, po zaimplementowaniu klas typowanych, można je zarejestrować i skonfigurować przy użyciu 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, co pokazuje poniższy kod 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 prawdziwy 'potencjał' ujawnia się w kolejnych sekcjach, gdzie pokazano, jak wszystkie żądania HTTP wysyłane przez HttpClient mogą mieć odporne zasady, takie jak ponawianie prób z wykładniczym opóźnieniem, obwody przerywające, funkcje zabezpieczeń z użyciem tokenów autoryzacyjnych lub każdą inną funkcję niestandardową. Wszystkie te czynności można wykonać, po prostu dodając polityki oraz delegując programy obsługi do zarejestrowanych typowanych klientów.

Dodatkowe zasoby