Utiliser IHttpClientFactory pour implémenter des requêtes HTTP résilientes
Conseil
Ce contenu est un extrait de l'eBook, Architecture des microservices .NET pour les applications .NET conteneurisées, disponible sur les .NET Docs ou sous forme de PDF téléchargeable gratuitement, consultable hors ligne.
IHttpClientFactory est un contrat implémenté par DefaultHttpClientFactory
, une fabrique avisée, disponible depuis .NET Core 2.1, pour la création d’instances HttpClient à utiliser dans vos applications.
Problèmes liés à la classe HttpClient d’origine disponible dans .NET
La classe HttpClient d’origine et connue peut être facilement utilisée, mais dans certains cas, elle n’est pas correctement utilisée par de nombreux développeurs.
Bien que cette classe implémente IDisposable
, la déclaration et l’instanciation dans une instruction using
n’est pas préférée, car lorsque l’objet HttpClient
est supprimé, le socket sous-jacent n’est pas immédiatement libéré, ce qui peut entraîner un problème d’épuisement du socket . Pour plus d’informations sur ce problème, consultez le billet de blog Vous utilisez HttpClient incorrect et il déstabilise votre logiciel.
Par conséquent, HttpClient
est destinée à être instanciée une fois et réutilisée tout au long de la durée d’une application. L’instanciation d’une classe HttpClient
pour chaque requête épuise le nombre de sockets disponibles sous de lourdes charges. Ce problème entraîne des erreurs SocketException
. Les approches possibles pour résoudre ce problème sont basées sur la création de l’objet HttpClient
en tant que singleton ou statique, comme expliqué dans cet article Microsoft sur l’utilisation de HttpClient. Il peut s’agir d’une bonne solution pour les applications console de courte durée ou similaires, qui s’exécutent quelques fois par jour.
Un autre problème que les développeurs rencontrent est l’utilisation d’une instance partagée de HttpClient
dans des processus de longue durée. Dans un cas où HttpClient est instancié en tant que singleton ou objet statique, il ne parvient pas à gérer les modifications DNS décrites dans le problème du dépôt GitHub dotnet/runtime.
Toutefois, le problème n’est pas vraiment avec HttpClient
en soi, mais avec le constructeur par défaut pour HttpClient, car il crée une nouvelle instance concrète de HttpMessageHandler, qui est celle qui a des problèmes d’épuisement des sockets et de modifications DNS mentionnées ci-dessus.
Pour résoudre les problèmes mentionnés ci-dessus et pour rendre HttpClient
instances gérables, .NET Core 2.1 a introduit deux approches, l’une d’elles étant IHttpClientFactory. Il s’agit d’une interface utilisée pour configurer et créer des instances HttpClient
dans une application via l’injection de dépendances (DI). Il fournit également des extensions pour l’intergiciel basé sur Polly afin de tirer parti de la délégation de gestionnaires dans HttpClient.
L’alternative consiste à utiliser SocketsHttpHandler
avec PooledConnectionLifetime
configuré. Cette approche est appliquée aux instances HttpClient
à longue durée de vie, static
ou uniques. Pour en savoir plus sur les différentes stratégies, consultez Instructions HttpClient pour .NET.
Polly est une bibliothèque de gestion des erreurs temporaires qui permet aux développeurs d’ajouter une résilience à leurs applications, en utilisant des stratégies prédéfinies de manière fluent et thread-safe.
Avantages de l’utilisation de IHttpClientFactory
L’implémentation actuelle de IHttpClientFactory, qui implémente également IHttpMessageHandlerFactory, offre les avantages suivants :
- Fournit un emplacement central pour nommer et configurer des objets de
HttpClient
logiques. Par exemple, vous pouvez configurer un client (Agent de service) préconfiguré pour accéder à un microservice spécifique. - Codifiez le concept de middleware sortant via la délégation de gestionnaires dans
HttpClient
et l’implémentation du middleware basé sur Polly pour tirer parti des stratégies de Polly pour la résilience. HttpClient
a déjà le concept de gestionnaires délégués pouvant être liés pour les requêtes HTTP émises. Vous pouvez inscrire des clients HTTP dans la fabrique et utiliser un gestionnaire Polly pour utiliser des stratégies Polly pour réessayer, CircuitBreakers, et ainsi de suite.- Gérez la durée de vie de HttpMessageHandler pour éviter les problèmes mentionnés qui peuvent se produire lors de la gestion vous-même de la durée de vie de
HttpClient
.
Conseil
Les instances HttpClient
injectées par DI peuvent être supprimées en toute sécurité, car la HttpMessageHandler
associée est gérée par l'usine. Les instances injectées HttpClient
sont temporaires du point de vue de l’injection de dépendances, tandis que les instances HttpMessageHandler
peuvent être considérées comme étendues. Les instances HttpMessageHandler
ont leurs propres étendues d’injection de dépendances (DI), distinctes des étendues d’application (par exemple, étendues de requête entrante ASP.NET). Pour plus d’informations, consultez Utilisation de HttpClientFactory dans .NET.
Remarque
L’implémentation de IHttpClientFactory
(DefaultHttpClientFactory
) est étroitement liée à l’implémentation d’interface utilisateur dans le package NuGet Microsoft.Extensions.DependencyInjection
. Si vous devez utiliser HttpClient
sans DI ou avec d’autres implémentations de DI, envisagez d’utiliser un static
ou un singleton HttpClient
avec PooledConnectionLifetime
configuré. Pour plus d’informations, consultez les instructions HttpClient pour .NET.
Plusieurs façons d’utiliser IHttpClientFactory
Il existe plusieurs façons d’utiliser IHttpClientFactory
dans votre application :
- Utilisation de base
- Utiliser des clients nommés
- Utiliser des clients typés
- Utiliser des clients générés
Par souci de concision, cette aide montre la façon la plus structurée d’utiliser IHttpClientFactory
, qui consiste à utiliser des clients typés (modèle Agent de service). Toutefois, toutes les options sont documentées et sont actuellement répertoriées dans cet article couvrant l’utilisation IHttpClientFactory
.
Remarque
Si votre application nécessite des cookies, il peut être préférable d’éviter d’utiliser des IHttpClientFactory dans votre application. Pour obtenir d’autres méthodes de gestion des clients, consultez Instructions relatives à l’utilisation de clients HTTP.
Comment utiliser des clients typés avec IHttpClientFactory
Alors, qu’est-ce qu’un « client typé » ? Il s’agit simplement d’un HttpClient
préconfiguré pour une utilisation spécifique. Cette configuration peut inclure des valeurs spécifiques telles que le serveur de base, les en-têtes HTTP ou les délais d’attente.
Le diagramme suivant montre comment les clients typés sont utilisés avec IHttpClientFactory
:
Figure 8-4. Utilisation de IHttpClientFactory
avec des classes client typées.
Dans l’image ci-dessus, un ClientService
(utilisé par un contrôleur ou un code client) utilise un HttpClient
créé par le IHttpClientFactory
inscrit. Cette fabrique affecte une HttpMessageHandler
d’un pool au HttpClient
. Le HttpClient
peut être configuré avec les stratégies de Polly lors de l’inscription du IHttpClientFactory
dans le conteneur DI avec la méthode d’extension AddHttpClient.
Pour configurer la structure ci-dessus, ajoutez IHttpClientFactory dans votre application en installant le package NuGet Microsoft.Extensions.Http
qui inclut la méthode d’extension AddHttpClient pour IServiceCollection. Cette méthode d’extension inscrit la classe de DefaultHttpClientFactory
interne à utiliser comme singleton pour l’interface IHttpClientFactory
. Il définit une configuration temporaire pour le HttpMessageHandlerBuilder. Ce gestionnaire de messages (objet HttpMessageHandler), obtenu à partir d’un pool, est utilisé par le HttpClient
retourné à partir de la fabrique.
Dans l’extrait de code suivant, vous pouvez voir comment AddHttpClient()
pouvez être utilisé pour inscrire des clients typés (agents de service) qui doivent utiliser 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>();
L'enregistrement des services clients comme indiqué dans l'extrait de code précédent fait en sorte que le DefaultClientFactory
crée un HttpClient
standard pour chaque service. Le client typé est inscrit en tant que temporaire avec le conteneur d’adresses DI. Dans le code précédent, AddHttpClient()
inscrit CatalogService, BasketService, OrderingService en tant que services temporaires afin qu’ils puissent être injectés et consommés directement sans avoir besoin d’inscriptions supplémentaires.
Vous pouvez également ajouter une configuration spécifique à l’instance dans l’inscription pour, par exemple, configurer l’adresse de base et ajouter des stratégies de résilience, comme indiqué dans les rubriques suivantes :
builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
Dans cet exemple suivant, vous pouvez voir la configuration de l’une des stratégies ci-dessus :
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
Vous trouverez plus d’informations sur l’utilisation de Polly dans l’article Suivant.
Durées de vie de HttpClient
Chaque fois que vous obtenez un objet HttpClient
à partir du IHttpClientFactory
, une nouvelle instance est retournée. Mais chaque HttpClient
utilise un HttpMessageHandler
qui est mis en commun et réutilisé par le IHttpClientFactory
pour réduire la consommation des ressources, tant que la durée de vie de la HttpMessageHandler
n’a pas expiré.
Le regroupement de gestionnaires est souhaitable, car chaque gestionnaire gère généralement ses propres connexions HTTP sous-jacentes ; la création de plus de gestionnaires que nécessaire peut entraîner des retards de connexion. Certains gestionnaires conservent également les connexions ouvertes indéfiniment, ce qui peut empêcher le gestionnaire de réagir aux modifications DNS.
Les objets HttpMessageHandler
du pool ont une durée de vie qui est la durée de vie pendant laquelle une instance de HttpMessageHandler
dans le pool peut être réutilisée. La valeur par défaut est de deux minutes, mais elle peut être remplacée pour chaque client typé. Pour la remplacer, appelez SetHandlerLifetime()
sur le IHttpClientBuilder qui est retourné lors de la création du client, comme indiqué dans le code suivant :
//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));
Chaque client typé peut avoir sa propre valeur de durée de vie de gestionnaire configurée. Définissez la durée de vie sur InfiniteTimeSpan
pour désactiver l’expiration du gestionnaire.
Implémenter vos classes client typées qui utilisent le httpClient injecté et configuré
À l’étape précédente, vous devez définir vos classes clientes typées, telles que les classes de l’exemple de code, telles que « BasketService », « CatalogService », « OrderingService », etc. : un client typé est une classe qui accepte un objet HttpClient
(injecté par le biais de son constructeur) et l’utilise pour appeler un service HTTP distant. Par exemple:
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;
}
}
Le client typé (CatalogService
dans l’exemple) est activé par DI (Injection de dépendances), ce qui signifie qu’il peut accepter n’importe quel service inscrit dans son constructeur, en plus de HttpClient
.
Un client typé est effectivement un objet temporaire, ce qui signifie qu’une nouvelle instance est créée chaque fois qu’une instance est nécessaire. Il reçoit une nouvelle instance HttpClient
chaque fois qu’elle est construite. Toutefois, les objets HttpMessageHandler
dans le pool sont les objets réutilisés par plusieurs instances HttpClient
.
Utiliser vos classes client typées
Enfin, une fois que vos classes typées ont été implémentées, vous pouvez les inscrire et les configurer avec AddHttpClient()
. Après cela, vous pouvez les utiliser partout où les services sont injectés par DI, comme dans le code de page Razor ou un contrôleur d’application web MVC, illustré dans le code ci-dessous d’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
}
}
}
Jusqu’à ce stade, l’extrait de code ci-dessus montre uniquement l’exemple d’exécution de requêtes HTTP régulières. Mais la « magie » se trouve dans les sections suivantes, où elle montre comment toutes les requêtes HTTP effectuées par HttpClient
peuvent avoir des stratégies résilientes telles que des nouvelles tentatives avec des interruptions exponentielles, des disjoncteurs, des fonctionnalités de sécurité à l’aide de jetons d’authentification ou même toute autre fonctionnalité personnalisée. Et toutes ces opérations peuvent être effectuées simplement en ajoutant des stratégies et en déléguant des gestionnaires à vos clients typés inscrits.
Ressources additionnelles
Directives HttpClient pour .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Utilisation de HttpClientFactory dans .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
utilisation de HttpClientFactory dans ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
Code source HttpClientFactory dans le référentiel GitHub
dotnet/runtime
https://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (résilience .NET et bibliothèque de gestion des erreurs temporaires)https://thepollyproject.azurewebsites.net/