.NET IHttpClientFactory + Microsoft Resilience-Wiederholungsversuch: Dieselbe HttpRequestMessage erneut gesendet, delegiC#

Ein Treffpunkt für C#-Programmierer
Anonymous
 .NET IHttpClientFactory + Microsoft Resilience-Wiederholungsversuch: Dieselbe HttpRequestMessage erneut gesendet, delegi

Post by Anonymous »

Ich habe einen HttpClient, der mit IHttpClientFactory und Microsoft.Extensions.Http.Resilience erstellt wurde, und versuche es bei 401 erneut. Pipeline:
  • Primär: HttpClientHandler (

    Code: Select all

    AllowAutoRedirect = false
    )
  • Delegieren: .AddHttpMessageHandler()
  • Outermost: .AddResilienceHandler(...) mit:

    Code: Select all

    HttpRetryStrategyOptions { MaxRetryAttempts = 1, Delay = TimeSpan.Zero }
  • Code: Select all

    ShouldHandle
    = 401
  • Code: Select all

    OnRetry
    ruft IAuthenticationTokenCache.ForceRefreshApiTokenAsync() auf (Singleton-Cache)

Der Auth-Handler stempelt einen Header pro Versuch:

Code: Select all

protected override async Task SendAsync(HttpRequestMessage request, CancellationToken ct)
{
request.Headers.Remove("X-Auth-Handler-Stamp");
request.Headers.TryAddWithoutValidation("X-Auth-Handler-Stamp", Guid.NewGuid().ToString("N"));

var jwt = await _tokenCache.GetTokenForApi().ConfigureAwait(false);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);

return await base.SendAsync(request, ct).ConfigureAwait(false);
}
Registrierung (vereinfacht):

Code: Select all

services.AddHttpClient("my-http-client")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AllowAutoRedirect = false
})
.AddHttpMessageHandler()
.AddResilienceHandler("RetryOnUnauthorized", (builder, sp) =>
{
var cache = sp.GetRequiredService(); // singleton
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 1,
Delay = TimeSpan.Zero,
ShouldHandle = new PredicateBuilder()
.HandleResult(r => r.StatusCode == HttpStatusCode.Unauthorized),
OnRetry = async _ => await cache.ForceRefreshApiTokenAsync().ConfigureAwait(false)
});
});
Erwartet:
Beim ersten 401 versucht der Resilience-Handler erneut, die gesamte Pipeline wird erneut ausgeführt und X-Auth-Handler-Stamp ändert sich.
Tatsächlich:
Ich sehe zwei HTTP-Aufrufe, aber die exakt gleichen Header (einschließlich X-Auth-Handler-Stamp) werden beide Male gesendet – als ob dieselbe HttpRequestMessage erneut gesendet würde und der Authentifizierungshandler nicht erneut ausgeführt würde.
Hinweise:
  • Es wird der korrekt benannte Client verwendet.
  • Cache ist Singleton und wird aktualisiert korrekt.
  • Keine Weiterleitungen, keine Proxy-Authentifizierung, Inhalte sind wiederverwendbar.
  • Das Hinzufügen von .Handle() hat das Verhalten nicht geändert.
Frage:
Gibt es eine Situation, in der das erneute Senden innerhalb von HttpClientHandler erfolgt (umgeht). Handler delegieren), oder verstehe ich das Wiederholungsverhalten/die Wiederholungsreihenfolge falsch? Was muss ich tun, um sicherzustellen, dass der Auth-Handler so ausgeführt wird, dass er das neue Token abruft?
Was ich versucht habe
  • Die Pipeline wurde mit IHttpClientFactory erstellt:

    Code: Select all

    HttpClientHandler
    (

    Code: Select all

    AllowAutoRedirect = false
    )
  • Code: Select all

    .AddHttpMessageHandler()
  • Code: Select all

    .AddResilienceHandler(...)
    last mit HttpRetryStrategyOptions hinzugefügt (Wiederholung bei 401, MaxRetryAttempts = 1, Delay = TimeSpan.Zero).
[*]Gesicherte Lebensdauern:
  • Code: Select all

    IAuthenticationTokenCache
    Singleton (sowohl vom Handler als auch von OnRetry verwendet).
  • Handler delegieren Transient.
[*]Verifiziert, dass ich den benannten Client verwende, der über diese Pipeline verfügt.

[*]Im Auth-Handler wurde ein Header pro Versuch gestempelt und bei jedem Versand überschrieben:

Code: Select all

request.Headers.Remove("X-Auth-Handler-Stamp");
request.Headers.TryAddWithoutValidation("X-Auth-Handler-Stamp", Guid.NewGuid().ToString("N"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);

[*]Auch dieselbe HttpRequestMessage über request.Options getaggt, um Versuche zu zählen:

Code: Select all

private static readonly HttpRequestOptionsKey AttemptKey = new("Attempt");
request.Options.TryGetValue(AttemptKey, out var attempt);
request.Options.Set(AttemptKey, attempt + 1);

[*]WireMock-Testaufbau:
  • Erster Aufruf mit altem Token → gibt 401 zurück und erweitert das Szenario.
  • 2. Aufruf mit neuem Token → sollte 200 zurückgeben.
[*].Handle() zu ShouldHandle hinzugefügt, nur für den Fall.

[*]Bestätigter Anforderungsinhalt ist wiederverwendbar (keine nicht durchsuchbaren Streams).


Was ich erwartet habe
  • Bei einem 401 sollte der Resilienz-Wiederholungsversuch erneut in die gesamte Pipeline eintreten, also:

    Code: Select all

    AuthenticationDelegatingHandler.SendAsync
    wird zweimal ausgeführt (einmal pro Versuch).
  • Code: Select all

    X-Auth-Handler-Stamp
    (GUID) ändert sich zwischen Versuchen.
  • Code: Select all

    AuthorizationDer 
    -Header wird beim erneuten Versuch mit dem aktualisierten Token überschrieben.
  • WireMock gleicht den zweiten Aufruf mit dem neuen Token ab und gibt 200 zurück.

Was tatsächlich passiert
  • Ich sehe zwei HTTP-Aufrufe, aber:

    Code: Select all

    X-Auth-Handler-Stamp
    ist in beiden Fällen identisch.
  • Header auf Wire-Ebene sind identisch; Es sieht so aus, als ob die dieselbe HttpRequestMessage erneut gesendet wurde.
  • Dies deutet darauf hin, dass das erneute Senden möglicherweise unter den delegierenden Handlern (innerhalb von HttpClientHandler) erfolgt oder der Authentifizierungshandler beim Wiederholungsversuch nicht erneut aufgerufen wird.

Jede minimale Reproduktion/Korrektur willkommen.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post