Usare IHttpClientFactory per l'implementazione di richieste HTTP resilienti
Suggerimento
Questo contenuto è un estratto dell'eBook "Microservizi .NET: Architettura per le applicazioni .NET incluse in contenitori", disponibile in .NET Docs o come PDF scaricabile gratuitamente e da poter leggere offline.
IHttpClientFactory è un contratto implementato da DefaultHttpClientFactory
, una solida factory, disponibile a partire da .NET Core 2.1, per la creazione di istanze HttpClient da usare nelle applicazioni.
Problemi con la classe HttpClient originale disponibile in .NET
L’originale e ben nota classe HttpClient può essere usata facilmente, ma in alcuni casi non viene usata correttamente dagli sviluppatori.
Anche se questa classe implementa IDisposable
, dichiararlo e instanziarlo all'interno di un'istruzione using
non è preferibile perché quando l'oggetto HttpClient
viene eliminato, il socket sottostante non viene rilasciato immediatamente, il che può causare un problema di esaurimento del socket. Per altre informazioni su questo problema, vedere il post di blog You're using HttpClient wrong and it is destabilizing your software (Uso errato di HttpClient e conseguente destabilizzazione del software).
La classe HttpClient
è pertanto destinata a essere avviata una sola volta e a essere riusata nell'arco della vita di un'applicazione. La creazione di un'istanza di una classe HttpClient
per ogni richiesta esaurisce il numero di socket disponibili con carichi voluminosi. Questo problema genera errori SocketException
. I possibili approcci per risolvere il problema si basano sulla creazione dell'oggetto HttpClient
come oggetto singleton o statico, come illustrato in questo articolo di Microsoft sull'utilizzo della classe 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 gli sviluppatori si trovano a dover affrontare si verificano quando si usa un'istanza condivisa di HttpClient
in processi a esecuzione prolungata. In una situazione in cui HttpClient viene istanziata come singleton o oggetto statico, non riesce a gestire le modifiche DNS come descritto in questo problema del repository GitHub dotnet/runtime.
Tuttavia, il problema non è in realtà con HttpClient
in sé, ma con il costruttore predefinito per HttpClient, perché crea una nuova istanza concreta di HttpMessageHandler, ovvero quella che presenta i problemi di esaurimento dei socket e le modifiche DNS menzionati in precedenza.
Per risolvere i problemi indicati in precedenza e rendere gestibili le istanze HttpClient
, .NET Core 2.1 ha introdotto due approcci, uno dei quali è IHttpClientFactory. Si tratta di un'interfaccia usata per configurare e creare istanze HttpClient
in un'app tramite inserimento delle dipendenze. 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. Tale approccio viene applicato a istanze static
o HttpClient
singleton di lunga durata. 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:
- Offre una posizione centrale per la denominazione e la configurazione di oggetti
HttpClient
logici. È ad esempio possibile configurare un client (agente di servizio) preconfigurato per l'accesso a un microservizio specifico. - Codificare il concetto di middleware in uscita tramite la delega di gestori in
HttpClient
e l'implementazione di middleware basato su Polly per sfruttarne i criteri di resilienza. HttpClient
include già il concetto di delega di gestori concatenati per le richieste HTTP in uscita. È possibile registrare i client HTTP nella factory ed è possibile usare un gestore Polly per utilizzare i criteri Polly per Retry, CircuitBreakers e così via.- Gestire la durata di HttpMessageHandler per evitare i problemi menzionati che possono verificarsi quando si gestiscono le durate di
HttpClient
personalmente.
Suggerimento
Le istanze HttpClient
inserite da DI possono essere eliminate in modo sicuro, perché l'oggetto associato HttpMessageHandler
è gestito dalla factory. Le istanze HttpClient
inserite sono temporanee dal punto di vista del DI, mentre le istanze HttpMessageHandler
possono essere considerate con ambito. Le istanze HttpMessageHandler
hanno ambiti DI specifici, separati dagli ambiti dell'applicazione, ad esempio, gli ambiti di richiesta in ingresso di ASP.NET. Per altre informazioni, vedere Uso di HttpClientFactory in .NET.
Nota
L'implementazione di IHttpClientFactory
(DefaultHttpClientFactory
) è strettamente legata all'implementazione di DI nel pacchetto NuGet Microsoft.Extensions.DependencyInjection
. Se è necessario usare HttpClient
senza DI o con altre implementazioni di DI, è consigliabile usare un static
o un HttpClient
singleton o con la configurazione PooledConnectionLifetime
. Per altre informazioni, vedere Linee guida httpClient per .NET.
Modi diversi di usare IHttpClientFactory
Esistono molti modi per usare IHttpClientFactory
nell'applicazione:
- Utilizzo di base
- Usare client denominati
- Usare client tipizzati
- Usare client generati
Per motivi di brevità, queste linee guida illustrano il modo più strutturato per usare IHttpClientFactory
, che consiste nell'usare client tipizzati (modello di agente di servizio). Tuttavia, tutte le opzioni sono documentate e sono attualmente elencate in questo articolo che illustra l'IHttpClientFactory
utilizzo.
Nota
Se l'app richiede cookie, potrebbe essere preferibile evitare di usare IHttpClientFactory nell'app. Per modi alternativi di gestione dei client, vedi Linee guida per l'uso dei client HTTP
Come usare i client tipizzati con IHttpClientFactory
Che cos'è un "client tipizzato"? È solo un HttpClient
oggetto 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 visualizza l'uso dei client tipizzati con IHttpClientFactory
.
Figura 8-4. Uso di IHttpClientFactory
con classi di client tipizzati.
Nell'immagine precedente, un oggetto ClientService
(usato da un controller o codice client) usa un oggetto HttpClient
creato dall'oggetto registrato IHttpClientFactory
. Questa factory assegna un HttpMessageHandler
in un pool all'oggetto HttpClient
. HttpClient
può essere configurato con i criteri di Polly durante la registrazione diIHttpClientFactory
nel contenitore di 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 AddHttpClientper IServiceCollection. Questo metodo di estensione registra la classe interna DefaultHttpClientFactory
da usare come singleton per l'interfaccia IHttpClientFactory
. Definisce una configurazione temporanea per l'oggetto HttpMessageHandlerBuilder. Questo gestore di messaggi (oggetto HttpMessageHandler), estratto da un pool, viene usato dall'oggetto HttpClient
restituito dalla factory.
Nel frammento di contenuto successivo è possibile vedere come usare AddHttpClient()
per registrare i client tipizzati (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, rende la creazione di DefaultClientFactory
uno standard HttpClient
per ogni servizio. Il client tipizzato viene registrato come temporaneo nel contenitore DI. Nel codice precedenteAddHttpClient()
registra CatalogService, BasketService, OrderingService come servizi temporanei, in modo che possano essere inseriti e utilizzati direttamente senza la necessità di registrazioni aggiuntive.
È inoltre possibile aggiungere una configurazione specifica dell'istanza nella registrazione, 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());
Nell’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 altre informazioni sull'uso di Polly, vedere l'articolo successivo.
Durate di HttpClient
Ogni volta che si ottiene un oggetto HttpClient
da IHttpClientFactory
viene restituita una nuova istanza. Tuttavia ogni HttpClient
usa un HttpMessageHandler
in pool e riusato da IHttpClientFactory
per ridurre il consumo di risorse, a condizione che la durata di HttpMessageHandler
non sia scaduta.
Il pooling dei gestori è opportuno poiché ogni gestore si occupa in genere di gestire le proprie connessioni HTTP sottostanti. La creazione di un numero superiore di gestori rispetto a quelli necessari può determinare ritardi di connessione. Alcuni gestori mantengono inoltre le connessioni aperte a tempo indefinito. Ciò può impedire al gestore di reagire alle modifiche DNS.
Gli oggetti HttpMessageHandler
nel pool hanno una durata, che è l'intervallo di tempo in cui un'istanza di HttpMessageHandler
nel pool può essere usata nuovamente. Il valore predefinito è due minuti, ma può essere sostituito in base al cliente tipizzato. Per eseguire l'override, chiamare SetHandlerLifetime()
nell'elemento IHttpClientBuilder restituito al momento della 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 o client denominato può avere un proprio valore configurato di durata del gestore. Impostare la durata su InfiniteTimeSpan
per disabilitare la scadenza del gestore.
Implementare le classi di client tipizzato che usano l'oggetto HttpClient inserito e configurato
È necessario avere definito in precedenza le classi di client tipizzato, ad esempio le classi nell'esempio di codice come "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. Ad 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 da DI (inserimento delle dipendenze), ovvero 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 HttpClient
ogni volta che viene costruita. Tuttavia, gli oggetti HttpMessageHandler
nel pool sono gli oggetti riutilizzati da più istanze HttpClient
.
Uso di classi di client tipizzato
Infine, dopo aver implementato le classi tipizzate, è possibile registrarle e configurarle con AddHttpClient()
. Dopodiché, è possibile utilizzare ovunque i servizi vengano inseriti tramite DI, ad esempio nel codice della pagina Razor o in un controller di app Web MVC, illustrato nel codice seguente di 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" è disponibile nelle sezioni seguenti, in cui viene illustrato come tutte le richieste HTTP effettuate da HttpClient
possono avere criteri resilienti, ad esempio i tentativi con backoff esponenziale, interruttori di circuito, funzionalità di sicurezza che usano token di autenticazione o anche qualsiasi altra funzionalità personalizzata. E tutte queste operazioni possono essere eseguite aggiungendo criteri e delegando i gestori per i client tipizzati registrati.
Risorse aggiuntive
Linee guida HttpClient per .NET
https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelinesUso di HttpClientFactory in .NET
https://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factoryUso di HttpClientFactory in ASP.NET Core
https://learn.microsoft.com/aspnet/core/fundamentals/http-requestsCodice sorgente HttpClientFactory nel
dotnet/runtime
repository GitHub
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (libreria .NET con funzionalità di resilienza e gestione degli errori temporanei)
https://thepollyproject.azurewebsites.net/