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
HttpRequestMessagedentro doExecuteAsync - 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:
StreamContentMultipartFormDataContentHttpRequestMessage
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