Verwenden von IHttpClientFactory zum Implementieren widerstandsfähiger HTTP-Anforderungen
Tipp
Dieser Inhalt ist ein Auszug aus dem eBook .NET Microservices Architecture for Containerized .NET Applications, verfügbar auf .NET Docs oder als kostenlose herunterladbare PDF, die offline gelesen werden kann.
IHttpClientFactory ist ein Vertrag, der von DefaultHttpClientFactory
implementiert wird, einer meinungsstarken Factory, die seit .NET Core 2.1 verfügbar ist, zur Erstellung von HttpClient-Instanzen für die Verwendung in Ihren Anwendungen.
Probleme mit der ursprünglichen HttpClient-Klasse, die in .NET verfügbar ist
Die ursprüngliche und bekannte HttpClient Klasse kann problemlos verwendet werden, aber in einigen Fällen wird sie von vielen Entwicklern nicht ordnungsgemäß verwendet.
Obwohl diese Klasse IDisposable
implementiert, wird das Deklarieren und Instanziieren in einer using
-Anweisung nicht bevorzugt, da der zugrunde liegende Socket nicht sofort freigegeben wird, wenn das HttpClient
Objekt gelöscht wird, was zu einem Socket-Erschöpfungs--Problem führen kann. Weitere Informationen zu diesem Problem finden Sie im Blogbeitrag Sie verwenden HttpClient falsch und destabilisieren Ihre Software.
Daher soll HttpClient
einmal instanziiert und während der gesamten Lebensdauer einer Anwendung wiederverwendet werden. Das Instanziieren einer HttpClient
-Klasse für jede Anforderung erschöpft die Anzahl der verfügbaren Sockets und führt zu hoher Auslastung. Dieses Problem führt zu SocketException
-Fehlern. Mögliche Ansätze zur Lösung dieses Problems basieren auf der Erstellung des HttpClient
Objekts als Singleton oder statisch, wie in diesem Microsoft-Artikel zur HttpClient-Verwendungerläutert. Dies kann eine gute Lösung für kurzlebige Konsolen-Apps oder ähnliche sein, die ein paar Mal täglich ausgeführt werden.
Ein weiteres Problem, auf das Entwickler stoßen, ist die Verwendung einer gemeinsam genutzten Instanz von HttpClient
in zeitintensiven Prozessen. In einer Situation, in der der HttpClient als Singleton oder ein statisches Objekt instanziiert wird, kann es die DNS-Änderungen nicht behandeln, wie in diesem Problem des GitHub-Repositorys von dotnet/runtime beschrieben.
Es geht jedoch nicht wirklich um HttpClient
an sich, sondern um den Standardkonstruktor für HttpClient, da er eine neue konkrete Instanz von HttpMessageHandler erstellt, die die oben erwähnten Probleme mit der Erschöpfung der Sockets und den DNS-Änderungen aufweist.
Um die oben genannten Probleme zu beheben und HttpClient
Instanzen verwaltbar zu machen, führte .NET Core 2.1 zwei Ansätze ein, einer davon IHttpClientFactory. Es ist eine Schnittstelle, die zum Konfigurieren und Erstellen von HttpClient
-Instanzen in einer App über Abhängigkeitsinjektion (DI) verwendet wird. Sie bietet auch Erweiterungen für auf Polly basierende Middleware, um die Vorteile der delegierenden Handler in HttpClient zu nutzen.
Die Alternative besteht darin, SocketsHttpHandler
mit dem konfigurierten PooledConnectionLifetime
zu verwenden. Dieser Ansatz wird auf langlebige, static
oder Singleton-HttpClient
Instanzen angewendet. Weitere Informationen zu verschiedenen Strategien finden Sie unter HttpClient-Richtlinien für .NET-.
Polly ist eine vorübergehende Fehlerbehandlungsbibliothek, die Entwicklern hilft, ihren Anwendungen Resilienz hinzuzufügen, indem sie einige vordefinierte Richtlinien auf fließende und threadsichere Weise verwenden.
Vorteile der Verwendung von IHttpClientFactory
Die aktuelle Implementierung von IHttpClientFactory, die auch IHttpMessageHandlerFactoryimplementiert, bietet die folgenden Vorteile:
- Stellt einen zentralen Speicherort zum Benennen und Konfigurieren logischer
HttpClient
Objekte bereit. Sie können beispielsweise einen Client (Service Agent) konfigurieren, der vorkonfiguriert ist, um auf einen bestimmten Microservice zuzugreifen. - Kodifizieren Sie das Konzept der ausgehenden Middleware mittels delegierender Handler in
HttpClient
und implementieren Sie Polly-basierte Middleware, um die Richtlinien von Polly für Resilienz zu nutzen. HttpClient
verfügt bereits über das Konzept der Delegierung von Handlern, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden können. Sie können HTTP-Clients in der Factory registrieren und einen Polly-Handler verwenden, um Polly-Richtlinien für Wiederholungen, CircuitBreakers usw. verwenden zu können.- Verwalten Sie die Lebensdauer von HttpMessageHandler, um die erwähnten Probleme oder Schwierigkeiten zu vermeiden, die auftreten können, wenn Sie die Lebensdauer von
HttpClient
selbst verwalten.
Tipp
Die per DI eingefügten HttpClient
-Instanzen können sicher verworfen werden, da der zugehörige HttpMessageHandler
von der Factory verwaltet wird. Eingefügte HttpClient
-Instanzen sind aus DI-Perspektive vorübergehend, während HttpMessageHandler
-Instanzen als bereichsbezogen betrachtet werden können. HttpMessageHandler
-Instanzen haben ihre eigenen DI-Bereiche, getrennt von den Anwendungsbereichen (z.B. die Bereiche für eingehende ASP.NET-Anfragen). Weitere Informationen finden Sie unter Verwenden von HttpClientFactory in .NET.
Anmerkung
Die Implementierung von IHttpClientFactory
(DefaultHttpClientFactory
) ist eng an die DI-Implementierung im Microsoft.Extensions.DependencyInjection
NuGet-Paket gebunden. Wenn Sie HttpClient
ohne DI oder mit anderen DI-Implementierungen verwenden müssen, sollten Sie einen static
- oder Singleton-HttpClient
mit eingerichteter PooledConnectionLifetime
verwenden. Weitere Informationen finden Sie unter HttpClient-Richtlinien für .NET-.
Mehrere Möglichkeiten zur Verwendung von IHttpClientFactory
Es gibt verschiedene Möglichkeiten, IHttpClientFactory
in Ihrer Anwendung zu verwenden:
- Grundlegende Nutzung
- Benannte Clients verwenden
- Verwenden von typierten Clients
- Verwenden von generierten Clients
Der Kürze halber zeigt dieser Leitfaden die strukturierteste Methode zur Verwendung von IHttpClientFactory
, nämlich die Verwendung von typisierten Clients (Service Agent-Muster). Allerdings sind alle Optionen dokumentiert und werden zurzeit in diesem Artikel zur Verwendung von IHttpClientFactory
aufgeführt.
Anmerkung
Wenn Ihre App Cookies erfordert, ist es möglicherweise besser, die Verwendung von IHttpClientFactory in Ihrer App zu vermeiden. Alternative Möglichkeiten zum Verwalten von Clients finden Sie unter Richtlinien für die Verwendung von HTTP-Clients.
Verwenden von typisierten Clients mit IHttpClientFactory
Was ist also ein "Typierter Client"? Es ist nur ein HttpClient
, der für eine bestimmte Verwendung vorkonfiguriert ist. Diese Konfiguration kann bestimmte Werte wie basisserver, HTTP-Header oder Timeouts enthalten.
Im folgenden Diagramm wird veranschaulicht, wie typisierte Clients mit IHttpClientFactory
verwendet werden:
Abbildung 8-4. Verwenden von IHttpClientFactory
mit typisierten Clientklassen.
In der obigen Abbildung verwendet ein (von einem Controller oder Clientcode verwendeter) ClientService
einen HttpClient
, der von der registrierten IHttpClientFactory
erstellt wird. Diese Factory weist einen HttpMessageHandler
aus einem Pool dem HttpClient
zu. Der HttpClient
kann bei Registrierung der IHttpClientFactory
im DI-Container mit der Erweiterungsmethode AddHttpClient mit Pollys Richtlinien konfiguriert werden.
Um die obige Struktur zu konfigurieren, fügen Sie ihrer Anwendung IHttpClientFactory hinzu, indem Sie das Microsoft.Extensions.Http
NuGet-Paket installieren, das die AddHttpClient Erweiterungsmethode für IServiceCollectionenthält. Diese Erweiterungsmethode registriert die interne DefaultHttpClientFactory
Klasse, die als Singleton für die Schnittstelle IHttpClientFactory
verwendet werden soll. Dadurch wird eine temporäre Konfiguration für HttpMessageHandlerBuilder definiert. Dieser Meldungshandler (HttpMessageHandler-Objekt), der aus einem Pool abgerufen wurde, wird von dem HttpClient
-Objekt verwendet, das von der Factory zurückgegebenen wird.
Im nächsten Codeausschnitt können Sie sehen, wie AddHttpClient()
zum Registrieren von typierten Clients (Service Agents) verwendet werden können, die HttpClient
verwenden müssen.
// 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>();
Wenn Sie die Clientdienste wie im vorherigen Codeausschnitt gezeigt registrieren, wird die DefaultClientFactory
einen Standard-HttpClient
für jeden Dienst erstellen. Der typisierte Client wird mit einem DI-Container als „vorübergehend“ registriert. Im vorherigen Code registriert AddHttpClient()
CatalogService, BasketService, Order Service als vorübergehende Dienste, damit sie direkt eingefügt und genutzt werden können, ohne dass zusätzliche Registrierungen erforderlich sind.
Sie können auch eine instanzspezifische Konfiguration in der Registrierung hinzufügen, um z. B. die Basisadresse zu konfigurieren und einige Resilienzrichtlinien hinzuzufügen, wie in den folgenden Beispielen gezeigt:
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
Im nächsten Beispiel sehen Sie die Konfiguration einer der oben genannten Richtlinien:
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Weitere Informationen zum Verwenden von Polly finden Sie im nächsten Artikel.
HttpClient-Lebensdauer
Jedes Mal, wenn Sie ein HttpClient
-Objekt aus dem IHttpClientFactory
abrufen, wird eine neue Instanz zurückgegeben. Jede HttpClient
verwendet jedoch eine HttpMessageHandler
, die von der IHttpClientFactory
gruppiert und wiederverwendet wird, um den Ressourcenverbrauch zu verringern, solange die Lebensdauer des HttpMessageHandler
nicht abgelaufen ist.
Die Poolerstellung von Handlern ist wünschenswert, da jeder Handler normalerweise seine eigenen zugrunde liegenden HTTP-Verbindungen verwaltet; Das Erstellen von mehr Handlern als erforderlich kann zu Verbindungsverzögerungen führen. Einige Handler halten Verbindungen auch unbegrenzt offen, wodurch verhindert werden kann, dass der Handler auf DNS-Änderungen reagiert.
Die HttpMessageHandler
Objekte im Pool haben eine Lebensdauer, die so lang ist, wie eine HttpMessageHandler
Instanz im Pool wiederverwendet werden kann. Der Standardwert beträgt zwei Minuten, kann jedoch für jeden typisierten Client überschrieben werden. Rufen Sie SetHandlerLifetime()
auf dem bei der Erstellung des Clients zurückgegebenen IHttpClientBuilder auf, um den Wert zu überschreiben, wie im folgenden Code gezeigt:
//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));
Jeder typierte Client kann über einen eigenen konfigurierten Handlerlebensdauerwert verfügen. Legen Sie die Lebensdauer auf InfiniteTimeSpan
fest, um das Ablaufen des Handlers zu deaktivieren.
Implementieren Sie Ihre typisierten Client-Klassen, die den injizierten und konfigurierten HttpClient verwenden.
Als vorheriger Schritt müssen Sie Ihre Typd Client-Klassen definiert haben, z. B. die Klassen im Beispielcode, z. B. "BasketService", "CatalogService", "OrderService" usw. – Ein typisierter Client ist eine Klasse, die ein HttpClient
-Objekt akzeptiert (durch den Konstruktor eingefügt) und verwendet es, um einen Remote-HTTP-Dienst aufzurufen. Zum Beispiel:
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;
}
}
Der typisierte Client (CatalogService
im Beispiel) wird per DI (Dependency Injection) aktiviert. Das bedeutet, dass dieser alle registrierten Dienste (zusätzlich zu HttpClient
) im Konstruktor akzeptieren kann.
Ein typierter Client ist effektiv ein vorübergehendes Objekt, d. h., eine neue Instanz wird jedes Mal erstellt, wenn ein Objekt benötigt wird. Bei jeder Konstruktion wird also eine neue HttpClient
-Instanz empfangen. Die HttpMessageHandler
Objekte im Pool sind jedoch die Objekte, die von mehreren HttpClient
Instanzen wiederverwendet werden.
Verwenden Sie Ihre typisierten Clientklassen
Nachdem Sie Ihre typisierten Klassen implementiert haben, können Sie sie registrieren und mit AddHttpClient()
konfigurieren. Danach können Sie sie überall dort verwenden, wo Dienste von DI eingefügt werden, z. B. im Razor-Seitencode oder einem MVC-Web-App-Controller, der im folgenden Code von eShopOnContainers gezeigt wird:
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
}
}
}
Bis zu diesem Punkt zeigt der obige Codeausschnitt nur das Beispiel für die Durchführung regulärer HTTP-Anforderungen. Aber die "Magie" kommt in den folgenden Abschnitten, in denen gezeigt wird, wie alle von HttpClient
vorgenommenen HTTP-Anforderungen widerstandsfähige Richtlinien wie Wiederholungen mit exponentiellem Backoff, Schaltschaltern, Sicherheitsfeatures mit Authentifizierungstoken oder sogar ein anderes benutzerdefiniertes Feature aufweisen können. Hierfür müssen Sie nur Richtlinien hinzufügen und den registrierten typisierten Clients Handler zuweisen.
Weitere Ressourcen
HttpClient-Richtlinien für .NET-https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Verwendung von HttpClientFactory in .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Verwenden von HttpClientFactory in ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
HttpClientFactory-Quellcode im
dotnet/runtime
GitHub-Repositoryhttps://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (Bibliothek für .NET-Resilienz und Behandlung vorübergehender Fehler)https://thepollyproject.azurewebsites.net/