Testar repetições e tempos limite em aplicativos .NET que usam Microsoft.Extensions.Http.Resilience

Tip

Novo para limitação? Saiba o que é limitação de taxa e como lidar com ela.

Visão geral
Meta: Confirme se o manipulador de resiliência HTTP .NET tenta novamente, recua e atinge o tempo limite conforme o esperado
Tempo: 15 minutos
Plugins:GenericRandomErrorPlugin, RetryAfterPlugin, LatencyPlugin
Pré-requisitos:Configurar o Proxy de Desenvolvimento, um aplicativo .NET que usa Microsoft.Extensions.Http.Resilience

Você adicionou AddStandardResilienceHandler() ao seu HttpClient. Como você sabe que funciona? As APIs que você chama raramente falham sob demanda, e testes de unidade que simulam HttpMessageHandler ignoram o pipeline de resiliência que você deseja testar.

O Proxy de Desenvolvimento fica entre seu aplicativo e a API. Ele retorna erros e respostas lentas ao seu aplicativo, portanto, seu aplicativo é executado inalterado e o manipulador de resiliência reage a respostas HTTP reais. Você vê todas as tentativas na saída do Proxy de Desenvolvimento.

O que o manipulador de resiliência padrão faz

Antes de testar, saiba o que esperar. Com opções padrão: AddStandardResilienceHandler()

Behavior Padrão
Novas tentativas ativadas HTTP 500 e acima, 408, 429, HttpRequestException e TimeoutRejectedException
Número de tentativas 3, com recuo exponencial e jitter, começando em 2 segundos
Retry-After cabeçalho Honrado. O manipulador aguarda o tempo que a API solicita.
Tempo limite da tentativa 10 segundos por tentativa
Tempo limite total 30 segundos para a solicitação, incluindo todas as novas tentativas

Para obter a lista completa de estratégias e seus padrões, consulte os padrões do manipulador de resiliência padrão.

Rotear seu aplicativo por meio do Dev Proxy

.NET usa o proxy do sistema, portanto, quando você inicia o Dev Proxy, ele intercepta as solicitações do aplicativo sem alterações de código. Para obter mais informações, consulte Usar o Proxy de Desenvolvimento com aplicativos .NET.

Simular erros transitórios

Crie uma configuração de Proxy de Desenvolvimento que faz as solicitações para sua API falharem com os erros para os quais o manipulador tenta novamente. Este exemplo usa https://api.contoso.com. Substitua-a pela URL da API que seu aplicativo usa.

Arquivo: 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

Adicione o RetryAfterPlugin antes do GenericRandomErrorPlugin em seu arquivo de configuração. Se você adicioná-la depois, o GenericRandomErrorPlugin falha a solicitação antes que o RetryAfterPlugin possa verificá-la.

No arquivo de erros, defina uma resposta de limitação de taxa e dois erros de servidor. O valor @dynamic define o cabeçalho Retry-After e informa RetryAfterPlugin para verificar se seu aplicativo aguarda esse tempo antes de chamar a API novamente.

Arquivo: 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
        }
      ]
    }
  ]
}

Inicie o Proxy de Desenvolvimento e execute seu aplicativo.

devproxy --config-file devproxyrc.json

Com uma taxa de falha de 50%, a maioria das solicitações se recupera após uma ou duas tentativas. Na saída do Proxy de Desenvolvimento, verifique se:

  • Após uma resposta 429, a próxima tentativa para a mesma URL vem após o Retry-After tempo. O Dev Proxy usa 5 segundos por padrão. Se o aplicativo chamar a API muito cedo, o RetryAfterPlugin relata isso e limita a taxa da solicitação.
  • Seu aplicativo não envia mais tentativas do que você configurou.
  • Solicitações que você não deseja tentar novamente, como uma POST que cria um registro, são enviadas apenas uma vez. Para excluí-los, chamar options.Retry.DisableForUnsafeHttpMethods() ou options.Retry.DisableFor(...).

Testar o que acontece quando as novas tentativas se esgotam

Novas tentativas ocultam falhas breves. Você também precisa saber o que seu aplicativo faz quando a API continua falhando. Inicie o Proxy de Desenvolvimento com uma taxa de falha de 100%:

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

O Dev Proxy mostra 4 tentativas para cada solicitação: a solicitação original e 3 novas tentativas. Após a última tentativa, o manipulador padrão não lança uma exceção. Ele retorna a última resposta de erro ao seu código. Verifique o que seu aplicativo faz com ele. Por exemplo, EnsureSuccessStatusCode() lança um HttpRequestExceptione GetStringAsync() lança também. Certifique-se de que seu aplicativo mostre uma mensagem útil ou recorra a uma alternativa, em vez de falhar ou mostrar um erro genérico.

timeouts de teste

APIs lentas disparam um caminho diferente no handler. Para testá-lo, use o LatencyPlugin para atrasar as respostas além do tempo limite de tentativa de 10 segundos.

Arquivo: 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
  }
}

Inicie o Proxy de Desenvolvimento e execute seu aplicativo. Cada tentativa leva mais de 10 segundos, de modo que o tempo limite da tentativa o cancela e o manipulador tenta novamente. Após 30 segundos, o tempo limite total cancela a solicitação e seu código obtém um TimeoutRejectedException. Verifique se seu aplicativo o captura e informa ao usuário o que aconteceu.

Tip

Para testar os mesmos cenários com suas próprias configurações de resiliência, altere os valores em sua AddStandardResilienceHandler(options => ...) chamada e execute as mesmas configurações de Proxy de Desenvolvimento novamente.

Próxima etapa

Saiba mais sobre como simular a limitação de taxa em qualquer API.

Consulte também