Testare i tentativi e i timeout nelle app .NET che usano Microsoft.Extensions.Http.Resilience

Tip

Non conosci ancora il throttling? Scopri che cos'è il throttling e come gestirlo.

A colpo d'occhio
Obiettivo: Verificare che il gestore della resilienza HTTP .NET ritenti, attenda progressivamente prima di riprovare e vada in timeout come previsto
Tempo: 15 minuti
Plugins:GenericRandomErrorPlugin, RetryAfterPlugin, LatencyPlugin
Prerequisiti:Configurare Dev Proxy, un'app .NET che usa Microsoft.Extensions.Http.Resilience

Hai aggiunto AddStandardResilienceHandler() al tuo HttpClient. Come si sa che funziona? Le API che chiami raramente falliscono su richiesta e i test unitari che fanno il mock di HttpMessageHandler saltano la pipeline di resilienza che si vuole testare.

Dev Proxy si trova tra l'app e l'API. Restituisce errori e risposte lente all'app, quindi l'app continua a funzionare invariata e il gestore di resilienza reagisce alle risposte HTTP reali. Ogni tentativo viene visualizzato nell'output di Dev Proxy.

Cosa fa l'handler di resilienza standard

Prima di eseguire il test, sappi cosa aspettarti. Con le opzioni predefinite, AddStandardResilienceHandler():

Behavior Impostazione predefinita
Tentativi attivati HTTP 500 e superiori, 408, 429, HttpRequestExceptione TimeoutRejectedException
Numero di tentativi 3, con backoff esponenziale e jitter, a partire da 2 secondi
Retry-After Intestazione Onorato. Il gestore attende il tempo richiesto dall'API.
Timeout del tentativo 10 secondi per tentativo
Tempo di attesa totale 30 secondi per la richiesta, inclusi tutti i tentativi

Per l'elenco completo delle strategie e dei relativi valori predefiniti, vedere Valori predefiniti del gestore di resilienza standard.

Instrada l'app tramite Dev Proxy

.NET usa il proxy di sistema, quindi quando avvii Dev Proxy intercetta le richieste dell'app senza modifiche al codice. Per altre informazioni, vedere Usare Dev Proxy con applicazioni .NET.

Simula errori transitori

Creare una configurazione del proxy di sviluppo che fa fallire le richieste alla tua API con gli errori che il gestore tenta nuovamente. Questo esempio usa https://api.contoso.com. Sostituiscilo con l'URL dell'API che la tua app chiama.

File: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
    },
    {
      "name": "GenericRandomErrorPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "transientErrors"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "transientErrors": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.schema.json",
    "errorsFile": "transient-errors.json",
    "rate": 50
  }
}

Caution

Aggiungi RetryAfterPlugin prima di GenericRandomErrorPlugin nel file di configurazione. Se lo si aggiunge dopo, GenericRandomErrorPlugin fa sì che la richiesta non vada a buon fine prima che RetryAfterPlugin possa controllarla.

Nel file degli errori definire una risposta di limitazione della frequenza e due errori del server. Il valore @dynamic imposta l'intestazione Retry-After e indica a RetryAfterPlugin di verificare che l'app attenda tale tempo prima di chiamare nuovamente l'API.

Archivio: transient-errors.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
  "errors": [
    {
      "request": {
        "url": "https://api.contoso.com/*"
      },
      "responses": [
        {
          "statusCode": 429,
          "headers": [
            {
              "name": "Retry-After",
              "value": "@dynamic"
            }
          ]
        },
        {
          "statusCode": 500
        },
        {
          "statusCode": 503
        }
      ]
    }
  ]
}

Avviare Dev Proxy ed eseguire l'app.

devproxy --config-file devproxyrc.json

Con un tasso di errore del 50%, la maggior parte delle richieste va a buon fine dopo uno o due tentativi. Nell'output di Dev Proxy verificare che:

  • Dopo una risposta 429, il tentativo successivo allo stesso URL arriva dopo il periodo di tempo Retry-After. Dev Proxy usa 5 secondi per impostazione predefinita. Se l'app chiama l'API troppo presto, RetryAfterPlugin lo segnala e limita la richiesta.
  • L'app non invia più tentativi rispetto a quelli configurati.
  • Le richieste che non si desidera ritentare, ad esempio un POST che crea un record, vengono inviate una sola volta. Per escluderli, chiamare options.Retry.DisableForUnsafeHttpMethods() o options.Retry.DisableFor(...).

Testare cosa accade quando si esauriscono i tentativi

I nuovi tentativi nascondono errori temporanei. È anche necessario sapere cosa fa l'app quando l'API continua a non funzionare. Avviare Dev Proxy con una frequenza di errore di 100%:

devproxy --config-file devproxyrc.json --failure-rate 100

Dev Proxy mostra 4 tentativi per ogni richiesta: la richiesta originale e 3 ritentativi. Dopo l'ultimo tentativo, il gestore standard non genera un'eccezione. Restituisce l'ultima risposta di errore al tuo codice. Controlla cosa fa la tua app con esso. Ad esempio, EnsureSuccessStatusCode() genera un'eccezione HttpRequestException, e GetStringAsync() genera anch'esso. Assicurarsi che l'app mostri un messaggio o un fallback utile, invece di arrestarsi in modo anomalo o visualizzare un errore generico.

Timeout dei test

Le API lente attivano un percorso diverso nell'handler. Per testarlo, usare LatencyPlugin per ritardare le risposte oltre il timeout di tentativo di 10 secondi.

File: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "LatencyPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "slowApi"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "slowApi": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/latencyplugin.schema.json",
    "minMs": 11000,
    "maxMs": 15000
  }
}

Avviare Dev Proxy ed eseguire l'app. Ogni tentativo richiede più di 10 secondi, quindi il timeout del tentativo lo annulla e il gestore ritenta. Dopo 30 secondi, il timeout totale annulla la richiesta e il codice riceve un TimeoutRejectedException. Verificare che l'app lo intercetta e indica all'utente cosa è successo.

Tip

Per testare gli stessi scenari con le proprie impostazioni di resilienza, modificare i valori nella AddStandardResilienceHandler(options => ...) chiamata ed eseguire di nuovo le stesse configurazioni di Dev Proxy.

Passaggio successivo

Scopri di più sulla simulazione del throttling in qualsiasi API.

Vedere anche