Quelle devrait être la durée de vie HttpClient
d'un client WebAPI?
Est-il préférable d’avoir une instance de la HttpClient
pour plusieurs appels?
Quels sont les frais généraux liés à la création et à la suppression d'une HttpClient
par demande, comme dans l'exemple ci-dessous (extrait de http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api -from-a-net-client ):
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:9000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// New code:
HttpResponseMessage response = await client.GetAsync("api/products/1");
if (response.IsSuccessStatusCode)
{
Product product = await response.Content.ReadAsAsync<Product>();
Console.WriteLine("{0}\t${1}\t{2}", product.Name, product.Price, product.Category);
}
}
HttpClient
a été conçu pour être réutilisé pour plusieurs appels . Même à travers plusieurs threads . La HttpClientHandler
a des informations d'identification et des cookies qui sont destinés à être réutilisés à travers les appels. Avoir une nouvelle instance HttpClient
nécessite de re-configurer tout cela . En outre, la propriété DefaultRequestHeaders
contient des propriétés destinées à plusieurs appels. Le fait de devoir réinitialiser ces valeurs à chaque demande annule le point.
Un autre avantage majeur de HttpClient
est la possibilité d’ajouter HttpMessageHandlers
au processus de demande/réponse afin d’appliquer des préoccupations transversales. Celles-ci peuvent être utilisées pour la journalisation, l'audit, la limitation, la gestion des redirections, la gestion hors ligne, la capture des métriques. Toutes sortes de choses différentes. Si un nouveau HttpClient est créé pour chaque demande, tous ces gestionnaires de messages doivent être configurés pour chaque demande et, en quelque sorte, tout état de niveau d'application partagé entre les demandes de ces gestionnaires doit également être fourni.
Plus vous utilisez les fonctionnalités de HttpClient
, plus vous verrez que la réutilisation d'une instance existante est logique.
Cependant, le plus gros problème, selon moi, est que lorsqu'une classe HttpClient
est supprimée, elle dispose de HttpClientHandler
, ce qui ferme ensuite de force la connexion TCP/IP
dans le pool de connexions géré par ServicePointManager
. Cela signifie que chaque demande avec une nouvelle HttpClient
nécessite de rétablir une nouvelle connexion TCP/IP
.
D'après mes tests, en utilisant un simple HTTP sur un réseau local, l'impact sur les performances est assez négligeable. J'imagine que cela est dû au fait qu'un TCP keepalive sous-jacent maintient la connexion ouverte même lorsque HttpClientHandler
tente de la fermer.
Sur les demandes qui vont sur Internet, j'ai vu une histoire différente. J'ai constaté une baisse de performance de 40% en raison de la nécessité de rouvrir la demande à chaque fois.
Je soupçonne que le coup sur une connexion HTTPS
serait encore pire.
Mon conseil est de conserver une instance de HttpClient pendant la durée de vie de votre application pour chaque API distincte à laquelle vous vous connectez.
Si vous souhaitez que votre application soit à l'échelle, la différence est énorme! En fonction de la charge, vous verrez des chiffres de performance très différents. Comme Darrel Miller le mentionne, HttpClient a été conçu pour être réutilisé dans toutes les demandes. Cela a été confirmé par les membres de l'équipe de la BCL qui l'ont écrit.
Un projet récent que j’avais consistait à aider un très grand et bien connu détaillant en ligne d’ordinateurs à s’élever pour le trafic de Black Friday/vacances pour certains nouveaux systèmes. Nous avons rencontré des problèmes de performances liés à l’utilisation de HttpClient. Puisqu'il implémente IDisposable
, les développeurs ont fait ce que vous feriez normalement en créant une instance et en la plaçant à l'intérieur d'une instruction using()
. Une fois que nous avons commencé les tests de charge, l'application a mis le serveur à genoux - oui, le serveur et pas seulement l'application. La raison en est que chaque instance de HttpClient ouvre un port sur le serveur. En raison de la finalisation non déterministe du CPG et du fait que vous travaillez avec des ressources informatiques couvrant plusieurs couches OSI , la fermeture des ports réseau peut prendre un certain temps. En fait, le système d'exploitation Windows lui-même peut prendre jusqu'à 20 secondes pour fermer un port (par Microsoft). Nous ouvrions des ports plus rapidement qu’ils ne pourraient être fermés - l’épuisement des ports de serveur, qui a porté le processeur à 100%. Mon correctif consistait à remplacer HttpClient par une instance statique qui résolvait le problème. Oui, il s’agit d’une ressource jetable, mais la différence de performance compense considérablement les frais généraux. Je vous encourage à faire des tests de charge pour voir comment votre application se comporte.
Vous pouvez également consulter la page WebAPI Guidance pour obtenir de la documentation et des exemples à l'adresse https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net -client
Portez une attention particulière à cet appel:
HttpClient est destiné à être instancié une fois et réutilisé tout au long de la vie d'une application. En particulier dans les applications serveur, la création d'une nouvelle instance HttpClient pour chaque requête épuise le nombre de sockets disponibles sous de lourdes charges. Cela entraînera des erreurs SocketException.
Si vous constatez que vous devez utiliser une HttpClient
statique avec différents en-têtes, adresses de base, etc., vous devez créer manuellement la HttpRequestMessage
et définir ces valeurs sur la HttpRequestMessage
. Ensuite, utilisez la HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)
Comme l'indique l'autre réponse, HttpClient
est destiné à être réutilisé. Cependant, la réutilisation d'une seule instance HttpClient
dans une application multithread signifie que vous ne pouvez pas modifier les valeurs de ses propriétés avec état, comme BaseAddress
et DefaultRequestHeaders
(vous ne pouvez donc les utiliser que si elles sont constantes dans votre application).
Une approche pour contourner cette limitation est d'encapsuler HttpClient
avec une classe qui duplique toutes les méthodes HttpClient
dont vous avez besoin (GetAsync
, PostAsync
etc.) et les délègue à un singleton HttpClient
. Cependant, c'est assez fastidieux (vous devrez aussi envelopper les méthodes extension aussi), et heureusement , il existe un autre moyen : continuer à créer de nouvelles instances HttpClient
, mais réutiliser le sous-jacent HttpClientHandler
. Assurez-vous simplement de ne pas disposer du gestionnaire:
HttpClientHandler _sharedHandler = new HttpClientHandler(); //never dispose this
HttpClient GetClient(string token)
{
//client code can dispose these HttpClient instances
return new HttpClient(_sharedHandler, disposeHandler: false)
{
DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue("Bearer", token)
}
};
}
Relatif aux sites Web volumineux mais pas directement à HttpClient. Nous avons l'extrait de code ci-dessous dans tous nos services.
// number of milliseconds after which an active System.Net.ServicePoint connection is closed.
const int DefaultConnectionLeaseTimeout = 60000;
ServicePoint sp =
ServicePointManager.FindServicePoint(new Uri("http://<yourServiceUrlHere>"));
sp.ConnectionLeaseTimeout = DefaultConnectionLeaseTimeout;
"Vous pouvez utiliser cette propriété pour vous assurer que les connexions actives d'un objet ServicePoint ne restent pas ouvertes indéfiniment. Cette propriété est destinée aux scénarios dans lesquels les connexions doivent être supprimées et rétablies périodiquement, telles que les scénarios d'équilibrage de charge.
Par défaut, lorsque KeepAlive a la valeur true pour une demande, la propriété MaxIdleTime définit le délai d'expiration pour la fermeture des connexions ServicePoint pour cause d'inactivité. Si le servicePoint a des connexions actives, MaxIdleTime n'a aucun effet et les connexions restent ouvertes indéfiniment.
Lorsque la propriété ConnectionLeaseTimeout est définie sur une valeur autre que -1 et qu'une fois le délai spécifié écoulé, une connexion ServicePoint active est fermée après le traitement d'une demande en définissant KeepAlive sur false dans cette demande . Cette valeur affecte toutes les connexions gérées. par l'objet ServicePoint. "
Lorsque vous avez des services derrière un CDN ou un autre point d'extrémité que vous souhaitez basculer, ce paramètre aide les appelants à vous suivre vers votre nouvelle destination. Dans cet exemple, 60 secondes après un basculement, tous les appelants doivent se reconnecter au nouveau point de terminaison. Pour ce faire, vous devez connaître vos services dépendants (ceux que VOUS appelez) et leurs points de terminaison.
Vous pouvez également vous reporter à ce billet de Simon Timms: https://aspnetmonsters.com/2016/08/2016 18-2-httpclientwrong/
Mais
HttpClient
est différent. Bien qu'il implémente l'interfaceIDisposable
, il s'agit en fait d'un objet partagé. Cela signifie que sous les capots il est réentrant) et que le fil est sûr. Au lieu de créer une nouvelle instance deHttpClient
pour chaque exécution, vous devez partager une seule instance deHttpClient
pendant toute la durée de vie de l'application. Voyons pourquoi.
Une chose à souligner, à savoir qu'aucune des notes «n'utilisez pas» de blogs, c'est qu'il ne faut pas uniquement tenir compte de BaseAddress et de DefaultHeader. Une fois que vous avez rendu HttpClient statique, il y a des états internes qui seront transmis aux demandes. Un exemple: vous vous authentifiez auprès d'un tiers avec HttpClient pour obtenir un jeton FedAuth (ignore pourquoi ne pas utiliser OAuth/OWIN/etc.), ce message de réponse a un en-tête Set-Cookie pour FedAuth, il est ajouté à votre état HttpClient. Le prochain utilisateur à se connecter à votre API enverra le cookie FedAuth de la dernière personne à moins que vous ne gériez ces cookies à chaque demande.