Se você está construindo um scraper, um cliente de API de terceiros ou qualquer sistema que precise fazer requisições HTTP em escala, eventualmente vai precisar de proxies. Seja para evitar rate limits, contornar bloqueios geográficos ou simplesmente distribuir a carga entre múltiplos IPs, entender como configurar C# HTTP proxy corretamente é essencial para desenvolvedores .NET.
Neste guia, vamos além do básico. Você vai aprender a configurar .NET HttpClient proxy com credenciais e bypass lists, usar o moderno SocketsHttpHandler para proxies por requisição, implementar resiliência com Polly, fazer scraping concorrente com Parallel.ForEachAsync, e construir um serviço real de rotação de C# residential proxies usando injeção de dependência.
HttpClient com HttpClientHandler e WebProxy
A forma mais comum de configurar um proxy em .NET é através do HttpClientHandler com a classe WebProxy. O HttpClientHandler é a implementação padrão de HttpMessageHandler que o HttpClient usa internamente.
Vamos começar com um exemplo completo que inclui autenticação, bypass list para URLs que não devem passar pelo proxy, e tratamento de erros:
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class BasicProxyExample
{
private readonly HttpClient _httpClient;
public BasicProxyExample()
{
// Configurar o proxy com credenciais
var proxy = new WebProxy("http://gate.proxyhat.com:8080", BypassOnLocal: false)
{
Credentials = new NetworkCredential("user-country-US", "PASSWORD"),
// URLs que não passam pelo proxy
BypassList = new[]
{
"localhost",
"127.0.0.1",
"192.168.*",
"*.internal.company.com"
}
};
// Configurar o handler com o proxy
var handler = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true,
// Importante para proxies HTTPS
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
// HttpClient com lifetime gerenciado pela aplicação
_httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
}
public async Task<string> FetchAsync(string url)
{
try
{
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex) when (ex.InnerException is System.Net.Sockets.SocketException)
{
Console.WriteLine($"Erro de conexão: {ex.Message}");
throw;
}
catch (TaskCanceledException)
{
Console.WriteLine("Timeout na requisição");
throw;
}
}
}
Alguns pontos importantes sobre este código:
- Singleton HttpClient: O
HttpClientdeve ser reutilizado. Não crie um novo para cada requisição — isso esgota sockets. - BypassList: Útil para não rotear tráfego interno pelo proxy, economizando banda e latência.
- AutomaticDecompression: Sempre configure para evitar desperdiçar CPU no servidor proxy.
Dica: Em aplicações ASP.NET Core, registre o
HttpClientviaAddHttpClient()no DI container para gerenciar corretamente o lifetime e evitar socket exhaustion.
SocketsHttpHandler: Controle Total e Proxy por Requisição
No .NET Core 2.1+ e .NET 5+, o SocketsHttpHandler é a implementação padrão e mais performática. Ele oferece controle fino sobre conexões, incluindo a capacidade de definir proxies por requisição através do callback ConnectCallback.
O PooledConnectionLifetime é crucial quando usando proxies residenciais rotativos: ele define quanto tempo uma conexão pode ser reutilizada antes de ser descartada, garantindo que você não reuse um IP que já expirou.
using System;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
public class SocketsHandlerProxyExample
{
private readonly HttpMessageHandler _handler;
public SocketsHandlerProxyExample()
{
_handler = new SocketsHttpHandler
{
// Conexões expiram após 2 minutos
// Crítico para proxies rotativos
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
// Tempo máximo para estabelecer conexão
ConnectTimeout = TimeSpan.FromSeconds(30),
// Habilitar HTTP/2 se suportado
EnableMultipleHttp2Connections = true,
// Callback para conexão customizada (proxy SOCKS5 ou HTTP)
ConnectCallback = async (context, token) =>
{
// Extrair proxy do contexto (pode ser dinâmico)
var proxyHost = "gate.proxyhat.com";
var proxyPort = 1080; // SOCKS5
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
{
NoDelay = true
};
try
{
await socket.ConnectAsync(proxyHost, proxyPort, token);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};
}
public HttpClient CreateClient()
{
return new HttpClient(_handler, disposeHandler: false)
{
Timeout = TimeSpan.FromSeconds(60)
};
}
// Exemplo com proxy HTTP tradicional via WebProxy
public HttpClient CreateClientWithWebProxy(string username, string password)
{
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential(username, password)
};
var handler = new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30)
};
return new HttpClient(handler);
}
}
Proxy Dinâmico por Requisição
Para cenários avançados onde cada requisição precisa de um proxy diferente, você pode usar o padrão de delegating handler:
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class DynamicProxyHandler : DelegatingHandler
{
private readonly IProxyPool _proxyPool;
public DynamicProxyHandler(IProxyPool proxyPool) : base(new SocketsHttpHandler())
{
_proxyPool = proxyPool;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Obter proxy do pool
var proxyEndpoint = await _proxyPool.GetNextProxyAsync();
// Clonar o handler base com novo proxy
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxyEndpoint.Url)
{
Credentials = new NetworkCredential(proxyEndpoint.Username, proxyEndpoint.Password)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromSeconds(30)
};
// Criar cliente temporário para esta requisição
using var client = new HttpClient(handler);
request.RequestUri = request.RequestUri;
return await client.SendAsync(request, cancellationToken);
}
}
public record ProxyEndpoint(string Url, string Username, string Password);
Resiliência com Polly: Retry e Circuit Breaker
Proxies podem falhar. IPs residenciais ficam indisponíveis, rate limits são atingidos, conexões são resetadas. O Polly é a biblioteca padrão para resiliência em .NET, e se integra perfeitamente com HttpClient via IHttpClientFactory.
using System;
using System.Net;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Extensions.Http;
public static class HttpClientBuilderExtensions
{
public static IHttpClientBuilder AddProxiedClient(this IHttpClientBuilder builder)
{
return builder
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "PASSWORD")
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(1)
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
}
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) +
TimeSpan.FromMilliseconds(Random.Shared.Next(0, 100)),
onRetry: (outcome, timeSpan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} após {timeSpan.TotalSeconds:F1}s " +
$"(Status: {outcome.Result?.StatusCode ?? 0})");
});
}
private static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (result, duration) =>
{
Console.WriteLine($"Circuit breaker aberto por {duration.TotalSeconds}s");
},
onReset: () =>
{
Console.WriteLine("Circuit breaker resetado");
});
}
}
// Registro no DI container
// services.AddHttpClient<IScraperService, ScraperService>()
// .AddProxiedClient();
A política de retry usa exponential backoff com jitter — essencial para evitar thundering herd quando múltiplos clientes retentam simultaneamente.
Scraping Concorrente com Parallel.ForEachAsync
Quando você precisa fazer centenas ou milhares de requisições, a concorrência é obrigatória. O Parallel.ForEachAsync, introduzido no .NET 6, é a forma moderna e eficiente de paralelizar trabalho assíncrono com controle de grau de paralelismo.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
public class ConcurrentScraper
{
private readonly HttpClient _httpClient;
private readonly int _maxDegreeOfParallelism;
public ConcurrentScraper(HttpClient httpClient, int maxDegreeOfParallelism = 20)
{
_httpClient = httpClient;
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
public async Task<ConcurrentBag<ScrapeResult>> ScrapeUrlsAsync(IEnumerable<string> urls)
{
var results = new ConcurrentBag<ScrapeResult>();
var stopwatch = Stopwatch.StartNew();
var successCount = 0;
var errorCount = 0;
var options = new ParallelOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
};
await Parallel.ForEachAsync(urls, options, async (url, token) =>
{
try
{
var result = await ScrapeSingleAsync(url, token);
results.Add(result);
Interlocked.Increment(ref successCount);
}
catch (Exception ex)
{
Interlocked.Increment(ref errorCount);
Console.WriteLine($"[ERROR] {url}: {ex.Message}");
}
});
stopwatch.Stop();
Console.WriteLine($"Completado: {successCount} sucessos, {errorCount} erros " +
$"em {stopwatch.Elapsed.TotalSeconds:F1}s");
return results;
}
private async Task<ScrapeResult> ScrapeSingleAsync(string url, CancellationToken token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
request.Headers.Add("Accept", "text/html,application/xhtml+xml");
using var response = await _httpClient.SendAsync(request, token);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync(token);
return new ScrapeResult(
Url: url,
StatusCode: (int)response.StatusCode,
Content: content,
ContentLength: content.Length
);
}
}
public record ScrapeResult(string Url, int StatusCode, string Content, int ContentLength);
// Uso:
// var urls = Enumerable.Range(1, 1000)
// .Select(i => $"https://httpbin.org/delay/1?page={i}");
// var results = await scraper.ScrapeUrlsAsync(urls);
Considerações sobre Grau de Paralelismo
- Proxies residenciais: Mantenha entre 5-20 conexões simultâneas por proxy para evitar bloqueios.
- Proxies datacenter: Podem aguentar mais, mas ainda assim respeite rate limits do servidor alvo.
- Memória: Cada requisição ocupa memória. Monitore uso em aplicações de longa duração.
Serviço de Pool de Proxies Rotativos com DI
Para um sistema de scraping profissional, você precisa de um serviço que gerencie proxies rotativos, tracking de uso, e seleção inteligente baseada em performance. Aqui está uma implementação completa usando injeção de dependência:
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
public interface IProxyPool
{
Task<ProxyInfo> GetNextProxyAsync();
void ReportSuccess(ProxyInfo proxy);
void ReportFailure(ProxyInfo proxy, Exception error);
}
public record ProxyInfo(
string Host,
int Port,
string Username,
string Password,
ProxyType Type,
string Country
);
public enum ProxyType { Residential, Datacenter, Mobile }
public class ProxyPoolService : IProxyPool, IDisposable
{
private readonly ConcurrentQueue<ProxyInfo> _availableProxies;
private readonly ConcurrentDictionary<string, ProxyStats> _stats;
private readonly ILogger<ProxyPoolService> _logger;
private readonly Timer _healthCheckTimer;
private readonly SemaphoreSlim _semaphore;
public ProxyPoolService(ILogger<ProxyPoolService> logger)
{
_logger = logger;
_availableProxies = new ConcurrentQueue<ProxyInfo>();
_stats = new ConcurrentDictionary<string, ProxyStats>();
_semaphore = new SemaphoreSlim(100); // Max 100 proxies simultâneos
// Inicializar pool com proxies
InitializePool();
// Health check a cada 5 minutos
_healthCheckTimer = new Timer(HealthCheck, null,
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
private void InitializePool()
{
// ProxyHat suporta geo-targeting via username
var countries = new[] { "US", "GB", "DE", "FR", "JP" };
foreach (var country in countries)
{
// Residential proxies
_availableProxies.Enqueue(new ProxyInfo(
Host: "gate.proxyhat.com",
Port: 8080,
Username: $"user-country-{country}",
Password: "YOUR_PASSWORD",
Type: ProxyType.Residential,
Country: country
));
// Mobile proxies (porta diferente ou flag no username)
_availableProxies.Enqueue(new ProxyInfo(
Host: "gate.proxyhat.com",
Port: 8080,
Username: $"user-country-{country}-mobile",
Password: "YOUR_PASSWORD",
Type: ProxyType.Mobile,
Country: country
));
}
_logger.LogInformation("Pool inicializado com {Count} proxies", _availableProxies.Count);
}
public async Task<ProxyInfo> GetNextProxyAsync()
{
await _semaphore.WaitAsync();
try
{
// Estratégia: round-robin com preferência por proxies com melhor histórico
ProxyInfo? bestProxy = null;
double bestScore = double.MinValue;
var temp = new List<ProxyInfo>();
while (_availableProxies.TryDequeue(out var proxy))
{
var stats = _stats.GetOrAdd(proxy.Username, _ => new ProxyStats());
var score = CalculateScore(stats);
if (score > bestScore)
{
if (bestProxy != null)
temp.Add(bestProxy);
bestProxy = proxy;
bestScore = score;
}
else
{
temp.Add(proxy);
}
}
// Re-enfileirar não selecionados
foreach (var p in temp)
_availableProxies.Enqueue(p);
if (bestProxy != null)
{
_stats.AddOrUpdate(bestProxy.Username,
new ProxyStats { LastUsed = DateTime.UtcNow },
(_, s) => { s.LastUsed = DateTime.UtcNow; return s; });
return bestProxy;
}
throw new InvalidOperationException("Nenhum proxy disponível");
}
finally
{
_semaphore.Release();
}
}
private double CalculateScore(ProxyStats stats)
{
// Score baseado em taxa de sucesso e tempo desde último uso
var successRate = stats.TotalRequests > 0
? (double)stats.SuccessCount / stats.TotalRequests
: 0.5;
var recencyPenalty = DateTime.UtcNow.Subtract(stats.LastUsed).TotalSeconds / 60.0;
return successRate * 100 - recencyPenalty;
}
public void ReportSuccess(ProxyInfo proxy)
{
_stats.AddOrUpdate(proxy.Username,
new ProxyStats { SuccessCount = 1, TotalRequests = 1 },
(_, s) => {
Interlocked.Increment(ref s.SuccessCount);
Interlocked.Increment(ref s.TotalRequests);
return s;
});
}
public void ReportFailure(ProxyInfo proxy, Exception error)
{
_stats.AddOrUpdate(proxy.Username,
new ProxyStats { TotalRequests = 1, FailureCount = 1 },
(_, s) => {
Interlocked.Increment(ref s.TotalRequests);
Interlocked.Increment(ref s.FailureCount);
return s;
});
_logger.LogWarning("Proxy {Username} falhou: {Error}", proxy.Username, error.Message);
}
private void HealthCheck(object? state)
{
_logger.LogInformation("Health check: {Available} proxies disponíveis",
_availableProxies.Count);
foreach (var kvp in _stats)
{
var rate = kvp.Value.TotalRequests > 0
? (double)kvp.Value.SuccessCount / kvp.Value.TotalRequests * 100
: 0;
_logger.LogDebug("Proxy {Key}: {Rate:F1}% sucesso", kvp.Key, rate);
}
}
public void Dispose()
{
_healthCheckTimer.Dispose();
_semaphore.Dispose();
}
}
public class ProxyStats
{
public int SuccessCount;
public int FailureCount;
public int TotalRequests;
public DateTime LastUsed;
}
// Registro no DI:
// services.AddSingleton<IProxyPool, ProxyPoolService>();
TLS, Certificate Pinning e Custom Root CA
Alguns cenários exigem controle total sobre TLS: proxies corporativos com inspeção SSL, certificate pinning para segurança extra, ou ambientes com autoridades certificadoras customizadas. O SocketsHttpHandler oferece SslClientAuthenticationOptions para isso.
using System;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
public class TlsConfiguredClient
{
private readonly HttpClient _httpClient;
public TlsConfiguredClient()
{
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "PASSWORD")
},
UseProxy = true,
SslOptions = new SslClientAuthenticationOptions
{
// TLS 1.2 ou superior obrigatório
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
// Ignorar erros de certificado (CUIDADO: apenas para desenvolvimento!)
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
// Em produção, valide propriamente
return true; // PERIGOSO - não usar em produção
},
// Certificate pinning - validar thumbprint específico
// RemoteCertificateValidationCallback = ValidateCertificatePin
}
};
_httpClient = new HttpClient(handler);
}
// Certificate pinning - apenas aceita certificados com thumbprint específico
private static bool ValidateCertificatePin(
object sender,
X509Certificate? cert,
X509Chain? chain,
SslPolicyErrors errors)
{
if (cert == null) return false;
// Thumbprints permitidos (SHA-1 do certificado)
var allowedThumbprints = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"A1B2C3D4E5F6...", // Thumbprint do certificado esperado
"1234567890AB..." // Backup/rotação de certificado
};
var thumbprint = cert.GetCertHashString();
return allowedThumbprints.Contains(thumbprint);
}
// Para proxies corporativos com custom root CA
public static HttpClient CreateWithCustomRootCA(string rootCAPath)
{
var rootCA = new X509Certificate2(rootCAPath);
var handler = new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
if (chain == null || cert == null) return false;
// Adicionar CA customizada ao chain
chain.ChainPolicy.ExtraTrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(rootCA);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
return chain.Build((X509Certificate2)cert);
}
}
};
return new HttpClient(handler);
}
public HttpClient Client => _httpClient;
}
Considerações de Segurança
- Nunca use
RemoteCertificateValidationCallbackretornandotrueem produção. - Certificate pinning é útil para APIs internas, mas complicado para sites públicos que rotacionam certificados.
- Proxies com inspeção SSL (man-in-the-middle) requerem instalação da CA customizada no trust store do SO.
Integração Completa: Scraper com Pool de Proxies
Vamos juntar tudo em um serviço completo de scraping que usa o pool de proxies, Polly para resiliência, e logging estruturado:
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
public interface IScraperService
{
Task<ScrapeResponse> ScrapeAsync(string url, string? country = null);
}
public record ScrapeResponse(string Url, int StatusCode, string Content, string ProxyUsed);
public class ProxiedScraperService : IScraperService
{
private readonly IProxyPool _proxyPool;
private readonly ILogger<ProxiedScraperService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public ProxiedScraperService(
IProxyPool proxyPool,
IHttpClientFactory httpClientFactory,
ILogger<ProxiedScraperService> logger)
{
_proxyPool = proxyPool;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public async Task<ScrapeResponse> ScrapeAsync(string url, string? country = null)
{
var proxy = await _proxyPool.GetNextProxyAsync();
_logger.LogDebug("Usando proxy {Proxy} para {Url}", proxy.Username, url);
using var client = CreateClientForProxy(proxy);
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("User-Agent", GetUserAgent());
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
request.Headers.Add("Accept-Language", "en-US,en;q=0.5");
using var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
_proxyPool.ReportSuccess(proxy);
_logger.LogInformation("Scrape bem-sucedido: {Url} ({Status})", url, response.StatusCode);
return new ScrapeResponse(url, (int)response.StatusCode, content, proxy.Username);
}
catch (Exception ex)
{
_proxyPool.ReportFailure(proxy, ex);
_logger.LogError(ex, "Falha ao scrape {Url} com proxy {Proxy}", url, proxy.Username);
throw;
}
}
private HttpClient CreateClientForProxy(ProxyInfo proxy)
{
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy($"http://{proxy.Host}:{proxy.Port}")
{
Credentials = new NetworkCredential(proxy.Username, proxy.Password)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
ConnectTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(60)
};
}
private static string GetUserAgent()
{
var agents = new[]
{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
};
return agents[Random.Shared.Next(agents.Length)] +
" (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
}
}
// Registro no Program.cs:
// builder.Services.AddSingleton<IProxyPool, ProxyPoolService>();
// builder.Services.AddHttpClient<IScraperService, ProxiedScraperService>();
Comparação: Abordagens de Proxy em .NET
| Abordagem | Prós | Contras | Melhor Para |
|---|---|---|---|
| HttpClientHandler + WebProxy | Simples, bem documentado, suporta credenciais e bypass | Proxy estático por cliente, menos controle | Proxies fixos, uso geral |
| SocketsHttpHandler | Mais performático, controle de pooling, ConnectCallback | API mais complexa | Alta performance, proxies dinâmicos |
| DelegatingHandler | Proxy por requisição, integração com IHttpClientFactory | Requer mais código, overhead por requisição | Proxies rotativos, pools |
| ConnectCallback | Controle total sobre conexão, suporta SOCKS5 | Complexidade máxima, manual | SOCKS5, proxies autenticados no nível TCP |
Key Takeaways
- Reuse HttpClient: Nunca crie
new HttpClient()por requisição. UseIHttpClientFactoryou singleton com handler gerenciado. - PooledConnectionLifetime: Configure para menos que o tempo de rotação do seu proxy residencial.
- Polly é obrigatório: Proxies falham. Retry com exponential backoff e circuit breaker protegem sua aplicação.
- Controle concorrência:
Parallel.ForEachAsynccomMaxDegreeOfParallelismlimitado previne bans e resource exhaustion. - Pool inteligente: Track success/failure por proxy para evitar IPs ruins e otimizar throughput.
- TLS matters: Para proxies corporativos ou pinning, configure
SslClientAuthenticationOptionscorretamente.
Conclusão
Configurar C# HTTP proxy corretamente vai além de setar uma URL no WebProxy. Para aplicações de scraping em escala, você precisa de: gerenciamento de lifetime de conexões, resiliência com retry e circuit breaker, controle de concorrência, e um pool de proxies que trackeie performance.
Com .NET HttpClient proxy via SocketsHttpHandler, Polly, e um serviço de pool bem arquitetado, você tem uma base sólida para C# residential proxies ou datacenter. O código acima é production-ready — copie, adapte, e escale.
Para proxies confiáveis com geo-targeting e IPs residenciais/móveis, confira os planos da ProxyHat e comece com créditos gratuitos.






