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.
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 HttpClient
są przejś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
:
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 HttpMessageHandler
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ą 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
wskazówki dotyczące HttpClient dla platformy .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Użycie elementu HttpClientFactory w .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Korzystanie z HttpClientFactory na platformie ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
kod źródłowy HttpClientFactory w repozytorium
dotnet/runtime
GitHubhttps://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (biblioteka odporności platformy .NET i obsługi błędów przejściowych)https://thepollyproject.azurewebsites.net/