.NET geliştiricileri için HTTP proxy kullanımı, web scraping, API rate limit aşımı, geo-targeting ve güvenli bağlantılar gibi senaryolarda kritik bir yetkinlik haline gelmiştir. Bu rehber, C# HTTP proxy kullanımının temellerinden ileri düzey konularına kadar her şeyi kapsıyor: .NET HttpClient proxy yapılandırması, rotating proxy havuzları, Polly ile retry mekanizmaları ve TLS sertifikası yönetimi.
HttpClient ve WebProxy: Temel Yapılandırma
.NET'te proxy kullanmanın en temel yolu HttpClientHandler ve WebProxy sınıflarını bir araya getirmektir. Proxy credentials, bypass list ve otomatik algılama özelliklerini buradan yönetebilirsiniz.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class BasicProxyExample
{
public static async Task Main()
{
// Proxy credentials ve adres yapılandırması
var proxyUrl = "http://user-country-US:your_password@gate.proxyhat.com:8080";
var webProxy = new WebProxy(new Uri(proxyUrl))
{
Credentials = CredentialCache.DefaultCredentials,
BypassProxyOnLocal = true,
BypassList = new[]
{
"localhost",
"127.0.0.1",
"*.internal.company.com"
}
};
// HttpClientHandler ile proxy ataması
var handler = new HttpClientHandler
{
Proxy = webProxy,
UseProxy = true,
UseDefaultCredentials = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
using var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
try
{
var response = await client.GetAsync("https://httpbin.org/ip");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {content}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
}
}
Bu örnekte ProxyHat'ın residential proxy ağına bağlanıyoruz. BypassList dizisi, belirli domain'lerin proxy üzerinden geçmeden doğrudan erişmesini sağlar. C# residential proxies kullanırken bu bypass mekanizması, iç ağ kaynaklarına erişim için önemlidir.
WebProxy ile Dinamik Credential Yönetimi
Production ortamlarında credentials genellikle environment variable'lardan veya secret manager'lardan gelir:
public class ProxyCredentialManager
{
private readonly string _proxyHost = "gate.proxyhat.com";
private readonly int _proxyPort = 8080;
public WebProxy CreateProxyWithCredentials(string username, string password)
{
var credentials = new NetworkCredential(username, password);
var proxyUri = new Uri($"http://{_proxyHost}:{_proxyPort}");
return new WebProxy(proxyUri)
{
Credentials = credentials,
UseDefaultCredentials = false
};
}
public WebProxy CreateGeoTargetedProxy(string country, string city, string password)
{
var username = $"user-country-{country}-city-{city.ToLowerInvariant()}";
return CreateProxyWithCredentials(username, password);
}
}
SocketsHttpHandler: İleri Düzey Proxy Kontrolü
.NET Core 2.1+ ve .NET 8'de SocketsHttpHandler, HttpClientHandler'dan daha fazla kontrol sunar. Per-request proxy seçimi, connection pooling ve TLS ayarları buradan yönetilir.
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class SocketsHandlerProxyExample
{
private readonly IProxyPool _proxyPool;
public SocketsHandlerProxyExample(IProxyPool proxyPool)
{
_proxyPool = proxyPool;
}
public async Task<string> FetchWithRotatingProxy(string url)
{
// Her istek için yeni bir proxy al
var proxyConfig = _proxyPool.GetNextProxy();
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxyConfig.ProxyUri)
{
Credentials = new NetworkCredential(proxyConfig.Username, proxyConfig.Password)
},
UseProxy = true,
// Connection pooling ayarları
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
MaxConnectionsPerServer = 10,
// TLS ayarları
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
// Custom validation logic buraya
return errors == System.Net.Security.SslPolicyErrors.None;
}
}
};
using var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
// Proxy configuration model
public record ProxyConfig(Uri ProxyUri, string Username, string Password);
// Proxy pool interface
public interface IProxyPool
{
ProxyConfig GetNextProxy();
}
PooledConnectionLifetime parametresi özellikle rotating proxy kullanırken kritiktir. Proxy değiştikçe eski bağlantıların temizlenmesini sağlar ve connection staleness sorunlarını önler.
Polly ile Retry ve Circuit Breaker
Proxy bağlantıları doğası gereği hata verebilir. Polly kütüphanesi, exponential backoff ile retry ve circuit breaker pattern'lerini uygulamanın standart yoludur.
using System;
using System.Net;
using System.Net.Http;
using Polly;
using Polly.Retry;
public class ResilientProxyClient
{
private readonly IAsyncPolicy<HttpResponseMessage> _retryPolicy;
private readonly IAsyncPolicy<HttpResponseMessage> _circuitBreakerPolicy;
private readonly HttpClient _httpClient;
public ResilientProxyClient()
{
// Retry policy: 5 deneme, exponential backoff
_retryPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => !r.IsSuccessStatusCode &&
(int)r.StatusCode >= 500 ||
r.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(5, retryAttempt =>
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
Console.WriteLine($"Retry {retryAttempt} after {delay.TotalSeconds}s");
return delay;
});
// Circuit breaker: 3 başarısızlık sonrası 30 saniye açık
_circuitBreakerPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (ex, breakDuration) =>
Console.WriteLine($"Circuit broke for {breakDuration.TotalSeconds}s"),
onReset: () => Console.WriteLine("Circuit reset")
);
// Handler yapılandırması
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential(
Environment.GetEnvironmentVariable("PROXYHAT_USER"),
Environment.GetEnvironmentVariable("PROXYHAT_PASS")
)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
_httpClient = new HttpClient(handler);
}
public async Task<HttpResponseMessage> ExecuteWithResilience(string url)
{
// Policy'leri birleştir: retry + circuit breaker
var strategy = Policy.WrapAsync(_circuitBreakerPolicy, _retryPolicy);
return await strategy.ExecuteAsync(async () =>
{
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return response;
});
}
}
Bu kombinasyon, .NET HttpClient proxy kullanımında en sık karşılaşılan sorunları ele alır: geçici ağ hataları, rate limiting ve proxy timeout'ları. Circuit breaker, sürekli başarısız olan proxy'lerin sistemi yormasını önler.
Parallel.ForEachAsync ile Eşzamanlı Scraping
.NET 8'de Parallel.ForEachAsync, I/O-bound işlemler için ideal bir concurrency pattern sunar. Proxy rotation ile birlikte kullanıldığında yüksek throughput sağlar.
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class ParallelProxyScraper
{
private readonly ConcurrentBag<ProxyConfig> _proxyPool;
private readonly ConcurrentDictionary<string, string> _results = new();
private int _proxyIndex = 0;
public ParallelProxyScraper(IEnumerable<ProxyConfig> proxies)
{
_proxyPool = new ConcurrentBag<ProxyConfig>(proxies);
}
public async Task<Dictionary<string, string>> ScrapeUrlsAsync(IEnumerable<string> urls)
{
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 20 // Eşzamanlı istek sayısı
};
await Parallel.ForEachAsync(urls, options, async (url, cancellationToken) =>
{
var proxyConfig = GetRotatingProxy();
try
{
var content = await FetchWithProxyAsync(url, proxyConfig, cancellationToken);
_results.TryAdd(url, content);
Console.WriteLine($"[OK] {url} via {proxyConfig.Username}");
}
catch (Exception ex)
{
Console.WriteLine($"[FAIL] {url}: {ex.Message}");
_results.TryAdd(url, $"Error: {ex.Message}");
}
});
return new Dictionary<string, string>(_results);
}
private ProxyConfig GetRotatingProxy()
{
// Round-robin rotation
var proxies = _proxyPool.ToArray();
var index = Interlocked.Increment(ref _proxyIndex) % proxies.Length;
return proxies[index];
}
private async Task<string> FetchWithProxyAsync(string url, ProxyConfig proxy, CancellationToken ct)
{
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxy.ProxyUri)
{
Credentials = new NetworkCredential(proxy.Username, proxy.Password)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(1)
};
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
var response = await client.GetAsync(url, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct);
}
}
// Kullanım örneği
public class Program
{
public static async Task Main()
{
var proxies = new List<ProxyConfig>
{
new(new Uri("http://gate.proxyhat.com:8080"), "user-country-US", "password1"),
new(new Uri("http://gate.proxyhat.com:8080"), "user-country-DE", "password1"),
new(new Uri("http://gate.proxyhat.com:8080"), "user-country-GB", "password1")
};
var scraper = new ParallelProxyScraper(proxies);
var urls = Enumerable.Range(1, 50)
.Select(i => $"https://httpbin.org/delay/{i % 3}");
var results = await scraper.ScrapeUrlsAsync(urls);
Console.WriteLine($"Completed {results.Count} requests");
}
}
Dependency Injection ile Rotating Proxy Havuzu
Production uygulamalarında proxy yönetimi DI container'a kaydedilmeli ve lifecycle boyunca yönetilmelidir. İşte tam bir rotating proxy pool servisi:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
// Domain models
public record ProxyEndpoint(string Host, int Port, string Username, string Password)
{
public Uri ProxyUri => new($"http://{Host}:{Port}");
}
public record ProxyPoolOptions
{
public string[] Countries { get; init; } = Array.Empty<string>();
public bool StickySession { get; init; } = false;
public int SessionDurationMinutes { get; init; } = 10;
}
// Interface
public interface IRotatingProxyPool
{
ProxyEndpoint GetProxy();
ProxyEndpoint GetProxy(string country);
void MarkProxyFailed(ProxyEndpoint proxy);
void RefreshPool();
}
// Implementation
public class RotatingProxyPool : IRotatingProxyPool
{
private readonly ConcurrentQueue<ProxyEndpoint> _availableProxies = new();
private readonly ConcurrentDictionary<ProxyEndpoint, int> _failedProxies = new();
private readonly ProxyPoolOptions _options;
private readonly ILogger<RotatingProxyPool> _logger;
private readonly string _baseUsername;
private readonly string _password;
public RotatingProxyPool(ProxyPoolOptions options, ILogger<RotatingProxyPool> logger)
{
_options = options;
_logger = logger;
_baseUsername = Environment.GetEnvironmentVariable("PROXYHAT_USERNAME")
?? throw new InvalidOperationException("PROXYHAT_USERNAME not set");
_password = Environment.GetEnvironmentVariable("PROXYHAT_PASSWORD")
?? throw new InvalidOperationException("PROXYHAT_PASSWORD not set");
InitializePool();
}
private void InitializePool()
{
foreach (var country in _options.Countries)
{
var proxy = new ProxyEndpoint(
"gate.proxyhat.com",
8080,
$"{_baseUsername}-country-{country}",
_password
);
_availableProxies.Enqueue(proxy);
}
_logger.LogInformation("Initialized proxy pool with {Count} endpoints",
_availableProxies.Count);
}
public ProxyEndpoint GetProxy()
{
if (_availableProxies.TryDequeue(out var proxy))
{
_availableProxies.Enqueue(proxy); // Round-robin
return proxy;
}
throw new InvalidOperationException("No proxies available in pool");
}
public ProxyEndpoint GetProxy(string country)
{
return new ProxyEndpoint(
"gate.proxyhat.com",
8080,
$"{_baseUsername}-country-{country}",
_password
);
}
public void MarkProxyFailed(ProxyEndpoint proxy)
{
_failedProxies.AddOrUpdate(proxy, 1, (_, count) => count + 1);
_logger.LogWarning("Proxy marked as failed: {Username}", proxy.Username);
}
public void RefreshPool()
{
_availableProxies.Clear();
_failedProxies.Clear();
InitializePool();
_logger.LogInformation("Proxy pool refreshed");
}
}
// DI Registration
public static class ProxyServiceExtensions
{
public static IServiceCollection AddRotatingProxyPool(
this IServiceCollection services,
Action<ProxyPoolOptions>? configure = null)
{
var options = new ProxyPoolOptions();
configure?.Invoke(options);
services.AddSingleton(options);
services.AddSingleton<IRotatingProxyPool, RotatingProxyPool>();
services.AddSingleton<ResilientHttpClientFactory>();
return services;
}
}
// Factory for creating resilient HttpClients
public class ResilientHttpClientFactory
{
private readonly IRotatingProxyPool _proxyPool;
private readonly ILoggerFactory _loggerFactory;
public ResilientHttpClientFactory(IRotatingProxyPool proxyPool, ILoggerFactory loggerFactory)
{
_proxyPool = proxyPool;
_loggerFactory = loggerFactory;
}
public HttpClient CreateClient()
{
var proxy = _proxyPool.GetProxy();
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxy.ProxyUri)
{
Credentials = new NetworkCredential(proxy.Username, proxy.Password)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1)
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
}
}
Program.cs ile Kayıt
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRotatingProxyPool(options =>
{
options.Countries = new[] { "US", "DE", "GB", "FR", "JP" };
options.StickySession = true;
options.SessionDurationMinutes = 5;
});
var app = builder.Build();
app.MapGet("/scrape", async (IRotatingProxyPool proxyPool, string url) =>
{
var proxy = proxyPool.GetProxy();
// Scrape logic...
return Results.Ok(new { ProxyUsed = proxy.Username });
});
app.Run();
TLS ve Sertifika Yönetimi
Proxy kullanırken TLS handshake'leri proxy üzerinden gerçekleşir. Certificate pinning ve custom CA gerektiren senaryolarda SslClientAuthenticationOptions kullanılır.
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
public class TlsProxyClient
{
private readonly X509Certificate2? _clientCertificate;
private readonly HashSet<string> _allowedCertificates;
public TlsProxyClient(string? clientCertPath = null, string? clientCertPassword = null)
{
if (!string.IsNullOrEmpty(clientCertPath))
{
_clientCertificate = new X509Certificate2(clientCertPath, clientCertPassword);
}
// Certificate pinning: İzin verilen public key thumbprint'leri
_allowedCertificates = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"A1B2C3D4E5F6...", // Hedef sunucunun thumbprint'i
"F7E8D9C0B1A2..." // Yedek certificate
};
}
public HttpClient CreateClientWithTls(ProxyEndpoint proxy)
{
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxy.ProxyUri)
{
Credentials = new NetworkCredential(proxy.Username, proxy.Password)
},
UseProxy = true,
SslOptions = new SslClientAuthenticationOptions
{
EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12,
ClientCertificates = _clientCertificate != null
? new X509CertificateCollection { _clientCertificate }
: null,
// Certificate pinning callback
RemoteCertificateValidationCallback = ValidateServerCertificate,
// Mutual TLS (mTLS) için
// LocalCertificateSelectionCallback = SelectClientCertificate
},
// Connection timeout'ları
ConnectTimeout = TimeSpan.FromSeconds(30),
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(60)
};
}
private bool ValidateServerCertificate(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
// Chain errors'ı log'la ama engelle
if (sslPolicyErrors != SslPolicyErrors.None)
{
Console.WriteLine($"TLS Warning: {sslPolicyErrors}");
}
if (certificate == null) return false;
using var cert2 = new X509Certificate2(certificate);
var thumbprint = cert2.Thumbprint;
// Certificate pinning kontrolü
if (_allowedCertificates.Count > 0)
{
return _allowedCertificates.Contains(thumbprint);
}
// Pinning yoksa standard validation
return sslPolicyErrors == SslPolicyErrors.None;
}
}
// Custom Root CA ile kullanım
public class CustomCaProxyClient
{
private readonly X509Certificate2 _rootCa;
public CustomCaProxyClient(string rootCaPath)
{
_rootCa = new X509Certificate2(rootCaPath);
}
public HttpClient CreateClientWithCustomCa(ProxyEndpoint proxy)
{
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy(proxy.ProxyUri)
{
Credentials = new NetworkCredential(proxy.Username, proxy.Password)
},
UseProxy = true,
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
if (cert == null || chain == null) return false;
// Custom root CA'ya göre validation
chain.ChainPolicy.ExtraStore.Add(_rootCa);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
return chain.Build((X509Certificate2)cert) &&
chain.ChainElements[^1].Certificate.Equals(_rootCa);
}
}
};
return new HttpClient(handler);
}
}
Production İpuçları ve Best Practices
C# HTTP proxy kullanımında karşılaşılan yaygın sorunları ve çözümlerini özetleyelim:
| Sorun | Nedeni | Çözüm |
|---|---|---|
| Connection timeout | Proxy yanıt vermiyor | Polly retry + proxy rotation |
| SSL certificate error | Proxy MITM yapıyor | Certificate pinning veya custom CA |
| Stale connections | Proxy değişti, connection havuzda kaldi | PooledConnectionLifetime ayarla |
| Rate limiting (429) | Tek IP'den çok istek | Rotating proxy + exponential backoff |
| Memory leak | HttpClient disposable değil |
IHttpClientFactory veya singleton client |
| DNS staleness | Uzun süren process'lerde DNS cache | PooledConnectionLifetime = 1-5 dakika |
Önemli:
HttpClientinstance'larınıusingbloğu içinde sürekli yaratmak socket exhaustion'a neden olur. YaIHttpClientFactorykullanın ya da singleton pattern uygulayın.
Key Takeaways
- HttpClientHandler basit senaryolar için yeterlidir; SocketsHttpHandler production için daha uygundur.
PooledConnectionLifetimerotating proxy kullanırken 1-5 dakika arasında tutun.- Polly ile retry + circuit breaker kombinasyonu, geçici hatalara dayanıklı sistemler oluşturur.
Parallel.ForEachAsync, .NET 8'de I/O-bound concurrent işlemler için ideal çözümdür.- TLS certificate pinning, MITM saldırılarına karşı koruma sağlar.
- DI ile proxy pool yönetimi, test edilebilir ve bakımı kolay kod üretir.
.NET HttpClient proxy entegrasyonu, modern C# uygulamalarında scraping, monitoring ve güvenli API erişimi için vazgeçilmez bir yetkinliktir. ProxyHat'ın residential ve datacenter proxy ağlarıyla bu pattern'leri uygulayarak, production-ready çözümler geliştirebilirsiniz. Detaylı fiyatlandırma ve lokasyon bilgisi için fiyatlandırma sayfasını inceleyebilirsiniz.






