HttpClient-Einzelinstanz mit unterschiedlichen Authentifizierungsheadern

78

Angesichts der Tatsache, dass der .net HttpClient unter Berücksichtigung der Wiederverwendung entwickelt wurde und langlebig sein soll und in kurzlebigen Fällen Speicherlecks gemeldet wurden . In welchen Richtlinien möchten Sie einen bestimmten Endpunkt mit verschiedenen Inhaber-Token (oder einem beliebigen Berechtigungsheader) ruhig aufrufen, wenn Sie den Endpunkt für mehrere Benutzer aufrufen?

private void CallEndpoint(string resourceId, string bearerToken) {
  httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("bearer", bearerToken);
  var response = await httpClient.GetAsync($"resource/{resourceid}");
}

Da der obige Code von einer beliebigen Anzahl von Threads in einer Webanwendung aufgerufen werden kann, ist es leicht möglich, dass der in der ersten Zeile festgelegte Header nicht derselbe ist, der beim Aufrufen der Ressource verwendet wird.

Was ist der empfohlene Ansatz zum Erstellen und Entsorgen von HttpClients für einen einzelnen Endpunkt, ohne Konflikte durch Sperren und Verwalten einer zustandslosen Webanwendung zu verursachen (meine derzeitige Praxis besteht darin, einen einzelnen Client pro Endpunkt zu erstellen)?


Lebenszyklus

Obwohl HttpClient die IDisposable-Schnittstelle indirekt implementiert, besteht die empfohlene Verwendung von HttpClient nicht darin, sie nach jeder Anforderung zu entsorgen. Das HttpClient-Objekt soll so lange leben, wie Ihre Anwendung HTTP-Anforderungen stellen muss. Wenn ein Objekt über mehrere Anforderungen hinweg vorhanden ist, können Sie DefaultRequestHeaders festlegen und müssen nicht bei jeder Anforderung Dinge wie CredentialCache und CookieContainer erneut angeben, wie dies bei HttpWebRequest erforderlich war.

Bronumski
quelle
Sprechen Sie über eine relativ kleine Anzahl verschiedener Auth-Header oder -Lose, die für jeden Benutzer eindeutig sind?
Todd Menier
@ToddMenier - Es wäre ein eindeutiger Header für jeden Benutzer. Es wäre, dass Benutzer oauth Token. Ich denke, Scott Hannen hat mich auf den richtigen Weg gebracht. Es sieht so aus, als wären einige Erweiterungsmethoden in Ordnung.
Bronumski
Hallo @Bronumski, kannst du uns mitteilen, wie du das gelöst hast? Ich habe das gleiche Problem mit mehreren Threads, die den gleichen Header hinzufügen, aber mit unterschiedlichem Inhalt.
Luis Mejia
@LuisMejia - Ich habe die Antwort von scotts mit Beispielen für GET und POST aktualisiert. Das gleiche Prinzip wird für alle anderen Methoden verwendet, die Sie implementieren möchten. Die Erweiterungsmethode enthält eine Aktion, mit der Sie die HttpRequest vor dem Senden bearbeiten können.
Bronumski
@Bronumski Danke für die Antwort ... scheint, als würde ich auf ähnliche Weise sendasync verwenden und eine Anfrage als Parameter mit den benutzerdefinierten Headern übergeben.
Luis Mejia

Antworten:

80

Wenn Ihre Header normalerweise gleich sind, können Sie die festlegen DefaultRequestHeaders. Sie müssen diese Eigenschaft jedoch nicht verwenden, um Header anzugeben. Wie Sie festgestellt haben, würde dies einfach nicht funktionieren, wenn Sie mehrere Threads mit demselben Client haben. Änderungen an den Standardheadern, die für einen Thread vorgenommen wurden, wirken sich auf Anforderungen aus, die an andere Threads gesendet werden.

Obwohl Sie Standardheader auf dem Client festlegen und auf jede Anforderung anwenden können, sind die Header tatsächlich Eigenschaften der Anforderung. Wenn die Header für eine Anforderung spezifisch sind, fügen Sie sie einfach der Anforderung hinzu.

request.Headers.Authorization = new AuthenticationHeaderValue("bearer", bearerToken);

Das heißt, Sie können die vereinfachten Methoden nicht verwenden, bei denen keine erstellt wird HttpRequest. Sie müssen verwenden

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)

hier dokumentiert .


Einige fanden es hilfreich, Erweiterungsmethoden zu verwenden, um den Code, der die Header aktualisiert, vom Rest einer Methode zu isolieren.

Beispiel für GET- und POST-Methoden, die über eine Erweiterungsmethode ausgeführt werden, mit der Sie den Anforderungsheader und mehr davon bearbeiten können, HttpRequestMessagebevor er gesendet wird:

public static Task<HttpResponseMessage> GetAsync
    (this HttpClient httpClient, string uri, Action<HttpRequestMessage> preAction)
{
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri);

    preAction(httpRequestMessage);

    return httpClient.SendAsync(httpRequestMessage);
}

public static Task<HttpResponseMessage> PostAsJsonAsync<T>
    (this HttpClient httpClient, string uri, T value, Action<HttpRequestMessage> preAction)
{
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri)
    {
        Content = new ObjectContent<T>
            (value, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null)
    };
    preAction(httpRequestMessage);

    return httpClient.SendAsync(httpRequestMessage);
}

Diese könnten dann wie folgt verwendet werden:

var response = await httpClient.GetAsync("token",
    x => x.Headers.Authorization = new AuthenticationHeaderValue("basic", clientSecret));
Scott Hannen
quelle
Tolle Lösung. Gerade implementiert. Verknüpfung nach folgendem Muster unter öffentlicher statischer Task hinzugefügt } void SetToken (HttpRequestMessage-Anforderung, Zeichenfolgentoken, Zeichenfolgentyp = "Träger")
Osa E
2
Was HttpClientHandler.Proxyist mit HttpClientHandler.CookieContainerund andere Eigenschaften HttpClientHandlerdavon können nicht in der eingestellt werden HttpRequestMessage? (oder können sie?)
David S.
@ DavidS. Haben Sie die Lösung für Proxy gefunden?
Jay Shah
1
Für Proxy, Cookie-Container usw., bei denen Sie spezielle Anforderungen für einzelne Anforderungen haben, besteht meiner Meinung nach die Empfehlung darin, benannte oder typisierte Clients zu verwenden, die die gewünschte Konfiguration für diese bestimmten Anforderungen haben, und dann einen unbenannten Client für alle anderen zu verwenden. docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
Hanf
@hemp - Ich stimme zu, aber diese Dokumentation gilt für Microsoft.Extensions.DependencyInjection( IServiceCollection), oder zumindest ist weniger klar, wie wir sie mit einem anderen IoC-Container oder keinem verwenden würden.
Scott Hannen