web-dev-qa-db-de.com

Was ist der Aufwand beim Erstellen eines neuen HttpClient pro Aufruf in einem WebAPI-Client?

Wie lang sollte HttpClient eines WebAPI-Clients sein?
Ist es besser, eine Instanz von HttpClient für mehrere Aufrufe zu haben?

Was ist der Aufwand für das Erstellen und Entsorgen eines HttpClient pro Anfrage, wie in dem folgenden Beispiel (aus http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api -von-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 wurde entworfen, um für mehrere Anrufe wiederverwendet zu werden . Sogar über mehrere Threads hinweg .__ Die Variable HttpClientHandler verfügt über Anmeldeinformationen und Cookies, die zur Wiederverwendung über mehrere Aufrufe hinweg bestimmt sind. Wenn Sie eine neue HttpClient-Instanz haben, müssen Sie das gesamte Zeug neu einrichten . Die DefaultRequestHeaders-Eigenschaft enthält außerdem Eigenschaften, die für mehrere Aufrufe gedacht sind. Wenn Sie diese Werte für jede Anforderung zurücksetzen müssen, wird der Punkt ungültig.

Ein weiterer großer Vorteil von HttpClient ist die Möglichkeit, der Anforderungs-/Antwort-Pipeline HttpMessageHandlers hinzuzufügen, um Querschnittsprobleme zu berücksichtigen. Dies kann für die Protokollierung, Überwachung, Drosselung, Umleitungshandhabung, Offlinehandhabung und Erfassung von Metriken sein. Allerlei verschiedene Dinge. Wenn für jede Anforderung ein neuer HttpClient erstellt wird, müssen alle diese Meldungshandler für jede Anforderung eingerichtet werden, und es muss auch ein beliebiger Status auf Anwendungsebene angegeben werden, der von Anforderungen für diese Handler gemeinsam genutzt wird. 

Je öfter Sie die Funktionen von HttpClient verwenden, desto mehr werden Sie feststellen, dass die Wiederverwendung einer vorhandenen Instanz sinnvoll ist.

Das größte Problem ist jedoch meiner Meinung nach, dass eine HttpClient-Klasse HttpClientHandler zur Verfügung steht, die dann die TCP/IP-Verbindung in dem Pool der Verbindungen, der von ServicePointManager verwaltet wird, zwangsweise beendet. Dies bedeutet, dass für jede Anforderung mit einer neuen HttpClient eine neue TCP/IP-Verbindung wiederhergestellt werden muss.

Bei meinen Tests, die nur HTTP in einem LAN verwenden, ist der Performance-Hit vernachlässigbar. Ich vermute, das liegt daran, dass es ein TCP - Keepalive gibt, das die Verbindung offen hält, selbst wenn HttpClientHandler versucht, sie zu schließen. 

Auf Anfragen, die über das Internet gehen, habe ich eine andere Geschichte gesehen. Ich habe einen Performance-Hit von 40% gesehen, weil ich die Anfrage jedes Mal erneut öffnen musste.

Ich vermute, der Schlag auf eine HTTPS-Verbindung wäre noch schlimmer.

Ich empfehle Ihnen, eine Instanz von HttpClient für die Lebensdauer Ihrer Anwendung zu behalten für jede einzelne API, zu der Sie eine Verbindung herstellen. 

182
Darrel Miller

Wenn Sie Ihre Anwendung skalieren möchten, ist der Unterschied RIESIG! Je nach Last sehen Sie sehr unterschiedliche Leistungszahlen. Wie Darrel Miller erwähnt, wurde der HttpClient für die Wiederverwendung in Anfragen entwickelt. Dies wurde von den Jungs des BCL-Teams bestätigt, die es geschrieben haben. 

Ein aktuelles Projekt, das ich hatte, war, einem sehr großen und bekannten Online-Computerhändler zu helfen, den Black Friday/Holiday-Traffic für einige neue Systeme auszurechnen. Bei der Verwendung von HttpClient sind einige Leistungsprobleme aufgetreten. Da es IDisposable implementiert, haben die Entwickler das getan, was Sie normalerweise tun würden, indem Sie eine Instanz erstellen und sie in eine using()-Anweisung einfügen. Nachdem wir mit dem Testen begonnen hatten, brachte die App den Server in die Knie - ja, der Server und nicht nur die App. Der Grund ist, dass jede Instanz von HttpClient einen Port auf dem Server öffnet. Aufgrund der nicht deterministischen Fertigstellung von GC und der Tatsache, dass Sie mit Computerressourcen arbeiten, die sich über mehrere OSI-Schichten erstrecken, kann das Schließen von Netzwerkports eine Weile dauern. Tatsächlich kann das Windows-Betriebssystem selbst bis zu 20 Sekunden dauern, um einen Port (pro Microsoft) zu schließen. Wir öffneten Ports schneller, als sie geschlossen werden könnten - Server-Port-Erschöpfung, die die CPU auf 100% stürzte. Meine Lösung bestand darin, den HttpClient in eine statische Instanz umzuwandeln, die das Problem löste. Ja, es ist eine verfügbare Ressource, aber der Overhead wird durch den Leistungsunterschied bei weitem aufgewogen. Ich empfehle Ihnen, einige Belastungstests durchzuführen, um zu sehen, wie sich Ihre App verhält.

Sie können die WebAPI Guidance-Seite auch nach Dokumentation und Beispielen unter https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a- net-client

Achten Sie besonders auf diesen Aufruf:

HttpClient soll einmalig instanziiert werden und während der gesamten Lebensdauer einer Anwendung wiederverwendet werden. Durch das Erstellen einer neuen HttpClient-Instanz für jede Anforderung wird insbesondere bei Serveranwendungen die Anzahl der verfügbaren Sockets erschöpft, die unter hoher Last verfügbar sind. Dies führt zu SocketException-Fehlern.

Wenn Sie feststellen, dass Sie ein statisches HttpClient mit verschiedenen Kopfzeilen, Basisadressen usw. verwenden müssen, müssen Sie das HttpRequestMessage manuell erstellen und diese Werte im HttpRequestMessage festlegen. Verwenden Sie dann die HttpClient:SendAsync(HttpRequestMessage requestMessage, ...)

60
Dave Black

Wie in den anderen Antworten angegeben, ist HttpClient zur Wiederverwendung gedacht. Die Wiederverwendung einer einzelnen HttpClient-Instanz in einer Multithread-Anwendung bedeutet jedoch, dass Sie die Werte ihrer stateful-Eigenschaften wie BaseAddress und DefaultRequestHeaders nicht ändern können (dh, sie können nur verwendet werden, wenn sie in Ihrer Anwendung konstant sind).

Um diese Einschränkung zu umgehen, können Sie HttpClient mit einer Klasse umgehen, die alle benötigten HttpClient-Methoden dupliziert (GetAsync, PostAsync etc) und an ein Singleton HttpClient delegiert. Dies ist jedoch ziemlich langwierig (Sie müssen auch die extension -Methoden einwickeln), und glücklicherweise gibt es einen anderen Weg : Erstellen Sie neue Instanzen mit dem Namen HttpClient, verwenden Sie jedoch den zugrunde liegenden HttpClientHandler erneut. Stellen Sie nur sicher, dass Sie den Handler nicht entsorgen:

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

Bezieht sich auf Websites mit hohem Volumen, jedoch nicht direkt auf HttpClient. Wir haben den Code-Ausschnitt in allen unseren Dienstleistungen.

        // 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;

Von 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 = true

"Sie können diese Eigenschaft verwenden, um sicherzustellen, dass die aktiven Verbindungen eines ServicePoint-Objekts nicht unbegrenzt geöffnet bleiben. Diese Eigenschaft ist für Szenarios gedacht, in denen Verbindungen regelmäßig abgebrochen und neu aufgebaut werden sollten, wie beispielsweise Lastausgleichszenarien.

Wenn KeepAlive für eine Anforderung auf true gesetzt ist, legt die MaxIdleTime-Eigenschaft standardmäßig das Zeitlimit für das Schließen von ServicePoint-Verbindungen aufgrund von Inaktivität fest. Wenn der ServicePoint über aktive Verbindungen verfügt, hat MaxIdleTime keine Auswirkungen und die Verbindungen bleiben unbegrenzt geöffnet.

Wenn die ConnectionLeaseTimeout-Eigenschaft auf einen anderen Wert als -1 festgelegt ist und nach Ablauf der angegebenen Zeit eine aktive ServicePoint-Verbindung geschlossen wird, nachdem eine Anforderung bearbeitet wurde, indem KeepAlive in dieser Anforderung auf false gesetzt wird. Diese Einstellung wirkt sich auf alle verwalteten Verbindungen aus durch das ServicePoint-Objekt. "

Wenn sich Dienste hinter einem CDN oder einem anderen Endpunkt befinden, für den ein Failover durchgeführt werden soll, können Anrufer mit dieser Einstellung an Ihr neues Ziel geleitet werden. In diesem Beispiel sollten 60 Sekunden nach einem Failover alle Anrufer erneut eine Verbindung zum neuen Endpunkt herstellen. Es ist jedoch erforderlich, dass Sie Ihre abhängigen Dienste (die Dienste, die Sie anrufen) und ihre Endpunkte kennen.

Sie können auch auf diesen Blogbeitrag von Simon Timms verweisen: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Aber HttpClient ist anders. Obwohl es die IDisposable-Schnittstelle implementiert, ist es tatsächlich ein gemeinsames Objekt. Dies bedeutet, dass es unter den Abdeckungen wiedereintrittsfähig ist und fadensicher ist. Anstatt für jede Ausführung eine neue Instanz von HttpClient zu erstellen, sollten Sie eine einzelne Instanz von HttpClient für die gesamte Lebensdauer der Anwendung freigeben. Schauen wir uns den Grund dafür an.

1
SvenAelterman

Eine Sache, die hervorgehoben werden soll, ist, dass keines der Blogs, die nicht verwendet werden, ein Hinweis ist, dass nicht nur BaseAddress und DefaultHeader berücksichtigt werden müssen. Sobald Sie HttpClient statisch gemacht haben, gibt es interne Zustände, die über Anforderungen hinweg übertragen werden. Ein Beispiel: Sie authentifizieren sich mit HttpClient bei einer dritten Partei, um ein FedAuth-Token zu erhalten (ignorieren Sie, warum Sie OAuth/OWIN/etc nicht verwenden). Diese Antwortnachricht enthält einen Set-Cookie-Header für FedAuth. Dies wird Ihrem HttpClient-Status hinzugefügt. Der nächste Benutzer, der sich bei Ihrem API anmeldet, sendet das FedAuth-Cookie der letzten Person, es sei denn, Sie verwalten diese Cookies bei jeder Anfrage.

0
escapismc