Quelle est la surcharge de la création d'un nouveau HttpClient par appel dans un client WebAPI?


162

Quelle devrait être la HttpClientdurée de vie d'un client WebAPI?
Est-il préférable d'avoir une instance de HttpClientpour plusieurs appels?

Quelle est la surcharge de création et de suppression d'une HttpClientdemande par requête, 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);
    }
}

Je ne suis pas sûr, vous pourriez cependant utiliser la Stopwatchclasse pour la comparer. Mon estimation serait qu'il serait plus logique d'en avoir un seul HttpClient, en supposant que toutes ces instances sont utilisées dans le même contexte.
Matthew

Réponses:


215

HttpClienta été conçu pour être réutilisé pour plusieurs appels . Même sur plusieurs threads. Le HttpClientHandlera des informations d'identification et des cookies qui sont destinés à être réutilisés lors d'appels. Avoir une nouvelle HttpClientinstance nécessite de reconfigurer tout cela. En outre, la DefaultRequestHeaderspropriété 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 HttpClientest la possibilité d'ajouter HttpMessageHandlersdans le pipeline de demandes / réponses pour appliquer des préoccupations transversales. Il peut s'agir de la journalisation, de l'audit, de la limitation, de la gestion des redirections, de la gestion hors ligne, de la capture de métriques. Toutes sortes de choses différentes. Si un nouveau HttpClient est créé à chaque demande, tous ces gestionnaires de messages doivent être configurés à chaque demande et, d'une manière ou d'une autre, tout état au niveau de l'application qui est 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 a du sens.

Cependant, le plus gros problème, à mon avis, est que lorsqu'une HttpClientclasse est supprimée, elle dispose HttpClientHandler, ce qui ferme ensuite de force la TCP/IPconnexion dans le pool de connexions géré par ServicePointManager. Cela signifie que chaque demande avec un nouveau HttpClientnécessite le rétablissement d'une nouvelle TCP/IPconnexion.

D'après mes tests, en utilisant HTTP simple sur un réseau local, le succès des performances est assez négligeable. Je soupçonne que c'est parce qu'il existe un keepalive TCP sous-jacent qui maintient la connexion ouverte même lorsque vous HttpClientHandleressayez de la fermer.

Sur les demandes qui vont sur Internet, j'ai vu une histoire différente. J'ai vu 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 HTTPSconnexion serait encore pire.

Mon conseil est de conserver une instance de HttpClient pour la durée de vie de votre application pour chaque API distincte à laquelle vous vous connectez.


5
which then forcibly closes the TCP/IP connection in the pool of connections that is managed by ServicePointManagerÊtes-vous sûr de cette déclaration? C'est difficile à croire. HttpClientme ressemble à une unité de travail qui est censée être instanciée souvent.
usr

2
@vkelman Oui, vous pouvez toujours réutiliser une instance de HttpClient même si vous l'avez créée avec un nouveau HttpClientHandler. Notez également qu'il existe un constructeur spécial pour HttpClient qui vous permet de réutiliser un HttpClientHandler et de supprimer le HttpClient sans interrompre la connexion.
Darrel Miller

2
@vkelman Je préfère garder le HttpClient, mais si vous préférez garder le HttpClientHandler, il gardera la connexion ouverte lorsque le deuxième paramètre est faux.
Darrel Miller du

2
@DarrelMiller On dirait donc que la connexion est liée à HttpClientHandler. Je sais que pour mettre à l'échelle, je ne veux pas détruire la connexion, donc je dois soit garder un HttpClientHandler et créer toutes mes instances HttpClient à partir de cela OU créer une instance HttpClient statique. Cependant, si le CookieContainer est lié au HttpClientHandler et que mes cookies doivent différer par demande, que recommandez-vous? Je voudrais éviter la synchronisation des threads sur un HttpClientHandler statique en modifiant son CookieContainer pour chaque requête.
Dave Black

2
@ Sana.91 Vous pourriez. Il serait préférable de l'enregistrer en tant que singleton dans la collection de services et d'y accéder de cette façon.
Darrel Miller

69

Si vous voulez que votre application évolue, la différence est ÉNORME! En fonction de la charge, vous verrez des chiffres de performance très différents. Comme le mentionne Darrel Miller, HttpClient a été conçu pour être réutilisé à travers les demandes. Cela a été confirmé par des membres de l'équipe de la BCL qui l'ont écrit.

Un projet récent que j'ai eu était d'aider un très grand détaillant informatique en ligne bien connu à évoluer pour le trafic du Black Friday / vacances pour certains nouveaux systèmes. Nous avons rencontré des problèmes de performances liés à l'utilisation de HttpClient. Depuis sa mise en œuvre 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 using()instruction. Une fois que nous avons commencé les tests de charge, l'application a mis le serveur à genoux - oui, le serveur, 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 de GC et du fait que vous travaillez avec des ressources informatiques qui s'étendent sur plusieurs couches OSI , la fermeture des ports réseau peut prendre un certain temps. En fait le système d'exploitation Windows lui-mêmepeut prendre jusqu'à 20 secondes pour fermer un port (selon Microsoft). Nous ouvrions les ports plus rapidement qu'ils ne pouvaient être fermés - l'épuisement des ports du serveur qui a martelé le processeur à 100%. Ma solution consistait à changer le HttpClient en une instance statique qui a résolu le problème. Oui, c'est une ressource jetable, mais les frais généraux sont largement compensés par la différence de performances. 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. Surtout dans les applications serveur, la création d'une nouvelle instance HttpClient pour chaque requête épuisera le nombre de sockets disponibles sous de lourdes charges. Cela entraînera des erreurs SocketException.

Si vous constatez que vous avez besoin d'utiliser un statique HttpClientavec des en-têtes, une adresse de base, etc. différents, vous devrez créer le HttpRequestMessagefichier manuellement et définir ces valeurs sur le HttpRequestMessage. Ensuite, utilisez leHttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

MISE À JOUR pour .NET Core : vous devez utiliser IHttpClientFactoryvia Dependency Injection pour créer des HttpClientinstances. Il gérera la durée de vie pour vous et vous n'avez pas besoin de le supprimer explicitement. Voir Effectuer des requêtes HTTP à l'aide de IHttpClientFactory dans ASP.NET Core


1
cet article contient des informations utiles pour ceux qui feront des tests de résistance ..!
Sana

9

Comme l'indiquent les autres réponses, HttpClientest destiné à être réutilisé. Cependant, la réutilisation d'une seule HttpClientinstance dans une application multithread signifie que vous ne pouvez pas modifier les valeurs de ses propriétés avec état, comme BaseAddresset DefaultRequestHeaders(vous ne pouvez donc les utiliser que si elles sont constantes dans votre application).

Une approche pour contourner cette limitation consiste à utiliser HttpClientune classe qui duplique toutes les HttpClientméthodes dont vous avez besoin ( GetAsync, PostAsyncetc.) et les délègue à un singleton HttpClient. Cependant, c'est assez fastidieux (vous devrez également envelopper les méthodes d'extension ), et heureusement, il existe un autre moyen : continuer à créer de nouvelles HttpClientinstances, mais réutiliser le sous-jacent HttpClientHandler. Assurez-vous simplement de ne pas supprimer le 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) 
       } 
    };
}

2
La meilleure façon de procéder est de conserver une instance HttpClient, puis de créer vos propres instances HttpRequestMessage locales, puis d'utiliser la méthode .SendAsync () sur HttpClient. De cette façon, il sera toujours thread-safe. Chaque HttpRequestMessage aura ses propres valeurs d'authentification / URL.
Tim P.

@TimP. pourquoi est-ce mieux? SendAsyncest beaucoup moins pratique que les méthodes dédiées telles que PutAsync, PostAsJsonAsyncetc.
Ohad Schneider

2
SendAsync vous permet de modifier l'URL et d'autres propriétés telles que les en-têtes et d'être toujours thread-safe.
Tim P.

2
Oui, le gestionnaire est la clé. Tant que cela est partagé entre les instances HttpClient, tout va bien. J'ai mal lu votre commentaire précédent.
Dave Black

1
Si nous conservons un gestionnaire partagé, devons-nous toujours nous occuper du problème DNS obsolète?
shanti

5

Lié aux sites Web à volume élevé 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-.NETFramework,Version%3Dv4.5.2); k (DevLang-csharp) & rd = vrai

"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, comme 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 en raison de l'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 une fois le temps spécifié écoulé, une connexion ServicePoint active est fermée après avoir traité une demande en définissant KeepAlive sur false dans cette demande. La définition de 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 de terminaison que vous souhaitez basculer, ce paramètre aide les appelants à vous suivre jusqu'à votre nouvelle destination. Dans cet exemple, 60 secondes après un basculement, tous les appelants doivent se reconnecter au nouveau point de terminaison. Cela nécessite que vous connaissiez vos services dépendants (les services que VOUS appelez) et leurs points de terminaison.


Vous mettez toujours beaucoup de charge sur le serveur en ouvrant et en fermant les connexions. Si vous utilisez des HttpClients basés sur des instances avec des HttpClientHandlers basés sur des instances, vous serez toujours confronté à l'épuisement des ports si vous ne faites pas attention.
Dave Black

Pas en désaccord. Tout est un compromis. Pour nous, suivre un CDN ou un DNS réacheminé est de l'argent à la banque par rapport à une perte de revenus.
Aucun remboursement Aucun retour

1

Vous pouvez également vous référer à ce billet de blog de Simon Timms: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Mais HttpClientc'est différent. Bien qu'il implémente l' IDisposableinterface, il s'agit en fait d'un objet partagé. Cela signifie que sous les couvercles, il est réentrant) et sans fil. Au lieu de créer une nouvelle instance de HttpClientpour chaque exécution, vous devez partager une seule instance de HttpClientpendant toute la durée de vie de l'application. Voyons pourquoi.


1

Une chose à souligner, qu'aucune des notes de blogs «ne pas utiliser en utilisant» est que ce ne sont pas seulement les BaseAddress et DefaultHeader que vous devez prendre en compte. Une fois que vous avez rendu HttpClient statique, il y a des états internes qui seront portés à travers les demandes. Un exemple: vous vous authentifiez auprès d'un tiers avec HttpClient pour obtenir un jeton FedAuth (ignorez pourquoi pas en utilisant OAuth / OWIN / etc), ce message de réponse a un en-tête Set-Cookie pour FedAuth, ceci est ajouté à votre état HttpClient. Le prochain utilisateur à se connecter à votre API enverra le cookie FedAuth de la dernière personne, sauf si vous gérez ces cookies à chaque demande.


0

Comme premier problème, bien que cette classe soit jetable, son utilisation avec l' usinginstruction n'est pas le meilleur choix car même lorsque vous supprimez un HttpClientobjet, le socket sous-jacent n'est pas immédiatement libéré et peut provoquer un problème sérieux nommé `` épuisement des sockets ''.

Mais il y a un deuxième problème HttpClientque vous pouvez rencontrer lorsque vous l'utilisez comme objet singleton ou statique. Dans ce cas, un singleton ou un statique HttpClientne respecte pas les DNSchangements.

dans .net core, vous pouvez faire la même chose avec HttpClientFactory quelque chose comme ceci:

public interface IBuyService
{
    Task<Buy> GetBuyItems();
}
public class BuyService: IBuyService
{
    private readonly HttpClient _httpClient;

    public BuyService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Buy> GetBuyItems()
    {
        var uri = "Uri";

        var responseString = await _httpClient.GetStringAsync(uri);

        var buy = JsonConvert.DeserializeObject<Buy>(responseString);
        return buy;
    }
}

Configurer les services

services.AddHttpClient<IBuyService, BuyService>(client =>
{
     client.BaseAddress = new Uri(Configuration["BaseUrl"]);
});

documentation et exemple sur ici

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.