Skip to main content

Request correta usando Polly no .NET | Polly | CSharp

  • Request correta usando Polly no .NET | Polly | CSharp

Objetivo

Demonstrar a forma correta e segura de fazer uma requisição HTTP usando Polly, evitando:

  • Reutilização de HttpRequestMessage
  • Reutilização de HttpContent
  • Problemas com Stream
  • Retry inseguro

Cenário Correto — Retry com HttpClient

Pontos importantes

  • Criar o HttpRequestMessage dentro do ExecuteAsync
  • Não reutilizar objetos mutáveis
  • Definir estratégia adequada de retry
  • Usar backoff exponencial com jitter

Exemplo Completo e Correto

using Polly;
using Polly.Wrap;
using System.Net.Http;
using System.Text;

async Task Main()
{
var httpClient = new HttpClient();

var retryPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => (int)r.StatusCode >= 500)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Console.WriteLine(
$"Retry {retryAttempt} após {timespan.TotalSeconds}s");
});

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));

var policyWrap = Policy.WrapAsync(retryPolicy, timeoutPolicy);

var response = await policyWrap.ExecuteAsync(async () =>
{
// 🔥 CRIAR request dentro do retry
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.exemplo.com/dados");

var json = "{\"nome\":\"Teste\"}";
request.Content = new StringContent(
json,
Encoding.UTF8,
"application/json");

return await httpClient.SendAsync(request);
});

Console.WriteLine($"Status: {response.StatusCode}");
}

Por que essa é a forma correta

1. HttpRequestMessage criado dentro do ExecuteAsync

Se criar fora:

var request = new HttpRequestMessage(...);
await retry.ExecuteAsync(() => client.SendAsync(request));

Problema:

  • O mesmo objeto é reutilizado
  • Pode estar parcialmente enviado
  • Pode ter conteúdo consumido

Forma correta:

await retry.ExecuteAsync(() =>
{
var request = new HttpRequestMessage(...);
return client.SendAsync(request);
});

2. HttpContent criado dentro do retry

Nunca reutilize:

  • StreamContent
  • MultipartFormDataContent
  • HttpRequestMessage

Exemplo Correto com Upload de Arquivo

Cenário seguro usando factory de stream.

async Task<HttpResponseMessage> EnviarArquivoAsync(
HttpClient client,
byte[] arquivoBytes)
{
var retryPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt =>
TimeSpan.FromSeconds(attempt));

return await retryPolicy.ExecuteAsync(async () =>
{
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.exemplo.com/upload");

using var content = new MultipartFormDataContent();

// 🔥 novo Stream a cada tentativa
var fileContent = new ByteArrayContent(arquivoBytes);

content.Add(fileContent, "file", "arquivo.txt");

request.Content = content;

return await client.SendAsync(request);
});
}

Exemplo com Func<Stream>

Quando o arquivo vem de disco.

async Task<HttpResponseMessage> UploadAsync(
HttpClient client,
Func<Stream> streamFactory)
{
var retryPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.RetryAsync(3);

return await retryPolicy.ExecuteAsync(async () =>
{
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.exemplo.com/upload");

using var content = new MultipartFormDataContent();

var fileContent = new StreamContent(streamFactory());

content.Add(fileContent, "file", "arquivo.txt");

request.Content = content;

return await client.SendAsync(request);
});
}

Uso:

await UploadAsync(client, () =>
File.OpenRead("arquivo.txt"));

Exemplo Correto via AddHttpClient

services.AddHttpClient("api")
.AddPolicyHandler(Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt =>
TimeSpan.FromMilliseconds(200 * attempt)));

Uso:

var client = factory.CreateClient("api");

var response = await client.GetAsync("/dados");

Cuidados Essenciais

  • Nunca reutilizar HttpRequestMessage
  • Nunca reutilizar StreamContent
  • Nunca reutilizar MultipartFormDataContent
  • Sempre criar conteúdo dentro do retry
  • Sempre limitar tentativas
  • Usar backoff exponencial
  • Evitar retry para 4xx
  • Garantir idempotência da operação

Estratégia Recomendada para Produção

  • Retry com backoff exponencial + jitter
  • Timeout menor que timeout global
  • Circuit breaker para proteger dependência
  • Logs estruturados em cada retry
  • Monitoramento de falhas transitórias