web-dev-qa-db-fra.com

Quel est le surcoût de la création d'un nouveau HttpClient par appel dans un client WebAPI?

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);
    }
}
133
Bruno Pessanha

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. 

182
Darrel Miller

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, ...)

60
Dave Black

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) 
       } 
    };
}
8
Ohad Schneider

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;

De https://msdn.Microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Net.ServicePoint.ConnectionLeaseTimeout);k(TargetFrameworkMoniker-.NETFrameworkV, 3 ); k (DevLang-csharp) & rd = true

"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.

4

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'interface IDisposable, 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 de HttpClient pour chaque exécution, vous devez partager une seule instance de HttpClient pendant toute la durée de vie de l'application. Voyons pourquoi.

1
SvenAelterman

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.

0
escapismc