Erros 500, 502, 503 e 504 das APIs: o que significam e como lidar com eles

Um código de estado de 500 para 599 significa que o servidor não cumpriu um pedido que parecia válido. O teu pedido não é o problema, por isso enviá-lo novamente pode resultar. Se deves enviá-lo novamente depende do código de estado e do que o pedido faz. Para as definições, consulte a RFC 9110, secção 15.6.

O que cada código de estado significa

Código de estado O que significa Tentar novamente?
500 Internal Server Error O servidor deparou-se com uma condição que não esperava Depende da API. Algumas APIs, como a Claude API, dizem para tentar novamente um 500 com backoff exponencial. Consulta a documentação da API.
502 Bad Gateway Um gateway ou proxy recebeu uma resposta inválida do servidor por trás dele Sim, se for seguro repetir o pedido
503 Service Unavailable O servidor está temporariamente sobrecarregado ou fora de serviço para manutenção, e deverá recuperar após algum tempo. O servidor pode enviar um cabeçalho Retry-After. Sim, depois do tempo Retry-After, se o servidor tiver enviado um
504 Gateway Timeout Um gateway ou proxy não recebeu uma resposta a tempo do servidor que está por trás dele Sim, se for seguro repeti-lo. O gateway desistiu de esperar, por isso não sabes se o servidor fez o trabalho.

Quais solicitações são seguras para tentar novamente

A RFC 9110 designa um método como idempotente quando se envia o mesmo pedido várias vezes e isso tem o mesmo efeito que enviá-lo uma vez. GET, HEAD, OPTIONS, TRACE, PUT, , e DELETE são idempotentes. POST E PATCH não são. De acordo com o RFC, um cliente não deve repetir automaticamente um pedido com um método não idempotente a menos que saiba que o pedido é idempotente de qualquer forma, ou possa perceber que o servidor nunca aplicou o pedido original. Para mais detalhes, veja Métodos idempotentes.

Uma nova tentativa de POST após um 502 ou 504 pode criar uma segunda encomenda ou enviar um segundo email. Algumas bibliotecas de repetição tentam novamente todos os métodos por defeito. Por exemplo, o handler de resiliência padrão .NET tenta POST novamente a menos que chame options.Retry.DisableForUnsafeHttpMethods(). Para mais detalhes, veja Criar aplicações HTTP resilientes.

Retry-After no erro 503

Um 503 pode incluir um cabeçalho Retry-After. O seu valor é ou um número de segundos, como 120, ou uma data HTTP, como Fri, 31 Dec 1999 23:59:59 GMT. O teu código tem de tratar de ambos. Para mais detalhes, veja Retry-After.

Pare de chamar uma API que continua a falhar

As retentativas ajudam a resolver falhas temporárias. Quando uma API está indisponível durante minutos, tentar novamente todos os pedidos adiciona carga a um servidor que já está a ter dificuldades, e os utilizadores esperam que cada retentativa falhe. Um circuit breaker regista falhas e, quando são demasiadas, deixa de chamar a API durante algum tempo e falha rapidamente. Depois desse tempo, deixa passar alguns pedidos para verificar se a API recuperou. Para mais informações, consulte o padrão Circuit Breaker. O handler de resiliência padrão do .NET inclui um circuit breaker que abre durante 5 segundos quando pelo menos 10% dos pedidos falham numa janela de 30 segundos com pelo menos 100 pedidos.

Como lidar com erros 5xx

  1. Tente novamente 502, 503 e 504 apenas para pedidos idempotentes. Para POST e PATCH, tente novamente apenas se a API documentar uma forma de os tornar seguros para repetir.
  2. Espera antes de tentares novamente. Use Retry-After quando o servidor enviar. Caso contrário, usa backoff exponencial com jitter aleatório e pára depois de algumas tentativas.
  3. Lê a documentação da API sobre o erro 500. Tente novamente apenas se a API disser que é seguro.
  4. Pare de invocar a API que continua a falhar. Use um circuit breaker para que a sua aplicação falhe rapidamente enquanto a API recupera.
  5. Diz ao utilizador o que aconteceu. Mostre "o serviço está a ter problemas, tente novamente mais tarde" em vez de um erro genérico ou de um rastreio da pilha.
const RETRYABLE_STATUS = new Set([502, 503, 504]);
const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"]);

function retryAfterMs(response) {
  const value = response.headers.get("retry-after");
  if (!value) return null;
  const seconds = Number(value);
  if (Number.isFinite(seconds)) return seconds * 1000;
  const date = Date.parse(value);
  return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
}

export async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const method = (options.method ?? "GET").toUpperCase();
  const canRetry = IDEMPOTENT_METHODS.has(method);

  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url, options);
    if (!RETRYABLE_STATUS.has(response.status) || !canRetry || attempt === maxRetries) {
      return response;
    }
    await response.body?.cancel();
    const backoff = 2 ** attempt * 1000 + Math.random() * 1000;
    const wait = retryAfterMs(response) ?? backoff;
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
}

Como testar se a sua aplicação lida com erros 5xx

Raramente vês um 5xx enquanto desenvolves, e não podes fazer uma API falhar à vontade. A forma como testas decide se encontras os bugs antes dos teus utilizadores.

Approach O que encontra Do que sentes falta
Aguardar produção Interrupções reais Tudo, até que um utilizador lhe clique
Simula a API nos teus testes, ou deixa o teu agente de programação escrever o mock Se a sua ramificação de erro é executada O teu cliente HTTP real e a biblioteca de retentativas, e quantas vezes realmente faz novas tentativas. A tua aplicação também precisa de um switch só de teste para aceder ao mock.
Invoca a API real e espera que ela falhe. Comportamento real Não podes fazer a API falhar a pedido
Interceta o tráfego real da tua aplicação e devolve erros 5xx com a frequência que escolheres O seu verdadeiro cliente HTTP, biblioteca de retentativas e circuit breaker Nada na tua aplicação muda, por isso não testa o teu código isoladamente. Guarda os testes unitários para isso.

Experimente na sua app

O Dev Proxy interceta os pedidos da sua aplicação e faz falhar uma parte deles com os erros que define, usando o GenericRandomErrorPlugin. A tua aplicação continua a chamar o URL real. Adiciona o plugin ao teu ficheiro de configuração e aponta o seu errorsFile para um ficheiro com os erros 5xx. Este exemplo utiliza https://api.contoso.com. Substitua-a pelo URL da API que a sua aplicação invoca.

Ficheiro: server-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": 500 },
        { "statusCode": 502 },
        {
          "statusCode": 503,
          "headers": [
            { "name": "Retry-After", "value": "10" }
          ]
        },
        { "statusCode": 504 }
      ]
    }
  ]
}

Por defeito, o plugin falha em 50% dos pedidos. Verifica na saída do Proxy de Desenvolvimento que a tua aplicação repete os pedidos GET e envia cada POST apenas uma vez. O Dev Proxy não verifica se a tua aplicação espera por Retry-After perante um 503, por isso compara tu mesmo os tempos dos pedidos. Depois começa o Dev Proxy com --failure-rate 100 para ver o que a tua app faz quando a API continua a falhar. Para mais informações, consulte taxa de falha no pedido de alteração. Para instalar Dev Proxy, consulte Configurar Dev Proxy.

Passos seguintes

Ver também