Usare IHttpClientFactory per implementare richieste HTTP resilienti
Consiglio
Questo contenuto è un estratto dell'eBook, Architettura di microservizi .NET per applicazioni .NET in contenitori, disponibile in documentazione .NET o come PDF scaricabile gratuito che può essere letto offline.
IHttpClientFactory è un contratto implementato da DefaultHttpClientFactory
, una factory con parere, disponibile a partire da .NET Core 2.1, per la creazione di istanze di HttpClient da usare nelle applicazioni.
Problemi con la classe HttpClient originale disponibile in .NET
La classe HttpClient originale e nota può essere usata facilmente, ma in alcuni casi non viene usata correttamente da molti sviluppatori.
Anche se questa classe implementa IDisposable
, dichiararla e instanziarla all'interno di un'istruzione using
non è preferibile perché, quando l'oggetto HttpClient
viene distrutto, il socket sottostante non viene rilasciato immediatamente, il che può causare un problema di esaurimento dei socket . Per altre informazioni su questo problema, vedere il post di blog Si sta usando HttpClient errato e sta destabilizzando il software.
Pertanto, HttpClient
deve essere istanziato una sola volta e riutilizzato per tutta la durata di un'applicazione. La creazione di un'istanza di una classe HttpClient
per ogni richiesta esaurirà il numero di socket disponibili con carichi elevati. Questo problema genererà errori SocketException
. I possibili approcci per risolvere il problema si basano sulla creazione dell'oggetto HttpClient
come singleton o statico, come illustrato in questo articolo di Microsoft sull'utilizzo di HttpClient. Questa può essere una buona soluzione per le app console di breve durata o simili, che vengono eseguite alcune volte al giorno.
Un altro problema che incontrano gli sviluppatori è l'uso di un'istanza condivisa di HttpClient
nei processi a lunga durata. In una situazione in cui HttpClient viene istanziato come singleton o un oggetto statico, non riesce a gestire le modifiche dei DNS come descritto in questo problema del repository GitHub dotnet/runtime.
Tuttavia, il problema non riguarda in realtà HttpClient
se stesso, ma con il costruttore predefinito per HttpClient, perché crea una nuova istanza concreta di HttpMessageHandler, ovvero quella con socket di esaurimento e i problemi di modifiche DNS indicati in precedenza.
Per risolvere i problemi indicati in precedenza e rendere gestibili le istanze di HttpClient
, .NET Core 2.1 ha introdotto due approcci, uno dei quali IHttpClientFactory. Si tratta di un'interfaccia usata per configurare e creare istanze di HttpClient
in un'app tramite Iniezione delle Dipendenze (DI). Fornisce anche estensioni per il middleware basato su Polly per sfruttare i vantaggi della delega dei gestori in HttpClient.
L'alternativa consiste nell'usare SocketsHttpHandler
con PooledConnectionLifetime
configurato. Questo approccio viene applicato alle istanze di lunga durata, static
o HttpClient
singole. Per altre informazioni sulle diverse strategie, vedere Linee guida httpClient per .NET.
Polly è una libreria di gestione degli errori temporanei che consente agli sviluppatori di aggiungere resilienza alle applicazioni, usando alcuni criteri predefiniti in modo fluente e thread-safe.
Vantaggi dell'uso di IHttpClientFactory
L'implementazione corrente di IHttpClientFactory, che implementa anche IHttpMessageHandlerFactory, offre i vantaggi seguenti:
- Fornisce una posizione centrale per la denominazione e la configurazione di oggetti
HttpClient
logici. Ad esempio, è possibile configurare un client (agente di servizio) preconfigurato per accedere a un microservizio specifico. - Codificare il concetto di middleware in uscita tramite la delega dei gestori in
HttpClient
e l'implementazione del middleware basato su Polly per sfruttare i criteri di Polly per la resilienza. -
HttpClient
ha già il concetto di delega dei gestori che possono essere collegati tra loro per le richieste HTTP in uscita. È possibile registrare i client HTTP nella factory ed è possibile usare un gestore Polly per usare i criteri Polly per Retry, CircuitBreakers e così via. - Gestisci la durata di HttpMessageHandler per evitare i problemi o le questioni menzionati che possono verificarsi quando gestisci la durata di
HttpClient
tu stesso.
Suggerimento
Le istanze di HttpClient
iniettate dalla Dependency Injection possono essere eliminate in sicurezza, perché il HttpMessageHandler
associato è gestito dalla factory. Le istanze di HttpClient
inserite vengono temporanee dal punto di vista dell'inserimento delle dipendenze, mentre le istanze di HttpMessageHandler
possono essere considerate come con ambito .
HttpMessageHandler
istanze hanno ambiti di inserimento delle dipendenze specifici, separati dagli ambiti dell'applicazione, ad esempio ASP.NET ambiti di richiesta in ingresso. Per altre informazioni, vedere Using HttpClientFactory in .NET.
Nota
L'implementazione di IHttpClientFactory
(DefaultHttpClientFactory
) è strettamente legata all'implementazione di Dependency Injection nel pacchetto NuGet Microsoft.Extensions.DependencyInjection
. Se è necessario utilizzare HttpClient
senza DI o con altre implementazioni DI, si consiglia di utilizzare un static
o un singleton HttpClient
con PooledConnectionLifetime
configurato. Per ulteriori informazioni, vedere Linee guida HttpClient per .NET.
Più modi per usare IHttpClientFactory
Esistono diversi modi per usare IHttpClientFactory
nell'applicazione:
- Utilizzo di base
- Usare clienti nominati
- Usare clienti con tipi definiti
- Usare i clienti generati
Per motivi di brevità, queste linee guida illustrano il modo più strutturato per usare IHttpClientFactory
, che consiste nell'utilizzare i client tipizzati (design pattern agente di servizio). Tuttavia, tutte le opzioni sono documentate e sono attualmente elencate in questo articolo relativo all'utilizzo IHttpClientFactory
.
Nota
Se l'app richiede cookie, potrebbe essere preferibile evitare di usare IHttpClientFactory nella tua app. Per modi alternativi per la gestione dei client, vedere Linee guida per l'uso di client HTTP.
Come usare client tipizzato con IHttpClientFactory
Quindi, che cos'è un "client tipizzato"? Si tratta solo di un HttpClient
preconfigurato per un uso specifico. Questa configurazione può includere valori specifici, ad esempio il server di base, le intestazioni HTTP o i timeout.
Il diagramma seguente illustra come i Client tipizzati vengono utilizzati con IHttpClientFactory
:
Figura 8-4. Uso di IHttpClientFactory
con classi client tipizzate.
Nell'immagine precedente, un ClientService
(usato da un controller o codice client) utilizza un HttpClient
creato da un IHttpClientFactory
registrato. Questo impianto assegna un HttpMessageHandler
da un pool al HttpClient
. Il HttpClient
può essere configurato con i criteri di Polly durante la registrazione del IHttpClientFactory
nel contenitore DI con il metodo di estensione AddHttpClient.
Per configurare la struttura precedente, aggiungere IHttpClientFactory nell'applicazione installando il pacchetto NuGet Microsoft.Extensions.Http
che include il metodo di estensione AddHttpClient per IServiceCollection. Questo metodo di estensione registra la classe DefaultHttpClientFactory
interna da usare come singleton per l'interfaccia IHttpClientFactory
. Definisce una configurazione temporanea per il HttpMessageHandlerBuilder. Questo gestore di messaggi (oggettoHttpMessageHandler), ricavato da un pool, viene utilizzato dal componente HttpClient
restituito dalla fabbrica.
Nel frammento di codice successivo è possibile vedere come usare AddHttpClient()
per registrare client tipiti (agenti di servizio) che devono usare 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>();
La registrazione dei servizi client, come illustrato nel frammento di codice precedente, fa sì che il DefaultClientFactory
crei un HttpClient
standard per ogni servizio. Il client tipizzato viene registrato come transitorio con il contenitore di iniezione delle dipendenze. Nel codice precedente AddHttpClient()
registra CatalogService, BasketService, OrderingService come servizi temporanei in modo che possano essere inseriti e utilizzati direttamente senza la necessità di registrazioni aggiuntive.
È anche possibile aggiungere una configurazione specifica dell'istanza nella registrazione a, ad esempio configurare l'indirizzo di base e aggiungere alcuni criteri di resilienza, come illustrato di seguito:
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
In questo esempio seguente è possibile visualizzare la configurazione di uno dei criteri precedenti:
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Per ulteriori dettagli sull'uso di Polly, consulta il prossimo articolo .
Durate di HttpClient
Ogni volta che si ottiene un oggetto HttpClient
dalla IHttpClientFactory
, viene restituita una nuova istanza. Tuttavia, ogni HttpClient
usa una HttpMessageHandler
che viene inserita in pool e riutilizzata dal IHttpClientFactory
per ridurre il consumo di risorse, purché la durata dell'HttpMessageHandler
non sia scaduta.
Il pooling di gestori è auspicabile perché ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti; la creazione di più gestori del necessario può comportare ritardi di connessione. Alcuni gestori mantengono anche le connessioni aperte a tempo indefinito, che possono impedire al gestore di reagire alle modifiche DNS.
Gli oggetti HttpMessageHandler
nel pool hanno una durata pari al tempo durante il quale un'istanza HttpMessageHandler
nel pool può essere riutilizzata. Il valore predefinito è di due minuti, ma può essere sottoposto a override per ogni client tipizzato. Per eseguirne l'override, chiamare SetHandlerLifetime()
sul IHttpClientBuilder restituito durante la creazione del client, come illustrato nel codice seguente:
//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));
Ogni client tipizzato può avere un proprio valore di durata del gestore configurato. Impostare la durata su InfiniteTimeSpan
per disabilitare la scadenza del gestore.
Implementare delle classi client tipate che utilizzano l'HttpClient iniettato e configurato
Come passaggio precedente, è necessario definire le classi Client tipate, ad esempio le classi nel codice di esempio, ad esempio "BasketService", "CatalogService", "OrderingService" e così via. Un client tipizzato è una classe che accetta un oggetto HttpClient
(inserito tramite il relativo costruttore) e lo usa per chiamare un servizio HTTP remoto. Per esempio:
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;
}
}
Il client tipizzato (CatalogService
nell'esempio) viene attivato dall'inserimento delle dipendenze, il che significa che può accettare qualsiasi servizio registrato nel relativo costruttore, oltre a HttpClient
.
Un client tipizzato è in effetti un oggetto temporaneo, ovvero viene creata una nuova istanza ogni volta che ne è necessaria una. Riceve una nuova istanza di HttpClient
ogni volta che viene costruita. Tuttavia, nel pool, gli oggetti HttpMessageHandler
sono quelli riutilizzati da più istanze di HttpClient
.
Usare le classi client tipate
Infine, dopo aver implementato le classi tipate, è possibile registrarle e configurarle con AddHttpClient()
. Dopo aver definito i servizi, è possibile utilizzarli ovunque vengano iniettati tramite Dependency Injection, ad esempio nel codice di una pagina Razor o in un controller di un'app web MVC, come mostrato nel seguente esempio di codice tratto da 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
}
}
}
Fino a questo punto, il frammento di codice precedente mostra solo l'esempio di esecuzione di normali richieste HTTP. Tuttavia, la "magia" si trova nelle sezioni seguenti, dove viene illustrato come tutte le richieste HTTP effettuate da HttpClient
possano avere criteri resilienti, come ritenti con backoff esponenziale, circuit breakers, funzionalità di sicurezza che utilizzano token di autenticazione, o anche qualsiasi altra funzionalità personalizzata. E tutte queste operazioni possono essere eseguite semplicemente aggiungendo politiche e delegando i gestori ai client tipizzati registrati.
Risorse aggiuntive
Linee guida per HttpClient per .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Uso di HttpClientFactory in .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Uso di HttpClientFactory in ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
codice sorgente HttpClientFactory nel repository GitHub
dotnet/runtime
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (resilienza .NET e libreria di gestione degli errori temporanei)https://thepollyproject.azurewebsites.net/