Warum C# HTTP Proxy-Konfiguration wichtig ist
Wenn Sie als .NET-Entwickler Web-Scraping, API-Automatisierung oder Preisüberwachung betreiben, kommen Sie an Proxys nicht vorbei. Websites erkennen und blockieren verdächtige Anfragemuster – und eine einzelne IP-Adresse, die hunderte Requests pro Minute sendet, ist definitiv verdächtig.
Die Verwendung von C# HTTP Proxys mit HttpClient ist nicht trivial. Es gibt Fallstricke bei Connection-Pooling, DNS-Resolution, TLS-Zertifikaten und Proxy-Rotation. Dieser Guide zeigt Ihnen, wie Sie .NET HttpClient mit Proxy korrekt konfigurieren und produktionsreife Lösungen für C# residential proxies implementieren.
Grundlagen: HttpClient mit WebProxy
Der klassische Weg in .NET verwendet HttpClientHandler mit einem WebProxy-Objekt. Das funktioniert für einfache Szenarien, hat aber einige Eigenheiten, die Sie kennen müssen.
using System.Net;
using System.Net.Http;
// Proxy mit Credentials konfigurieren
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password"),
BypassProxyOnLocal = true,
BypassList = new[] { "localhost", "127.0.0.1", "*.internal.local" }
};
// HttpClientHandler mit Proxy erstellen
var handler = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true,
UseDefaultCredentials = false,
AllowAutoRedirect = true,
MaxAutomaticRedirections = 5
};
// HttpClient instanziieren (als Singleton verwenden!)
var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
// Request senden
var response = await client.GetAsync("https://httpbin.org/ip");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {content}");Wichtig: Instanziieren Sie HttpClient nicht pro Request. Das führt zu Socket-Exhaustion. Verwenden Sie IHttpClientFactory oder einen Singleton.
Proxy-URL-Format mit ProxyHat
Bei ProxyHat können Sie Geo-Targeting und Session-Flags direkt im Benutzernamen übergeben:
// US-IP mit Session-Sticky
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US-session-abc123", "your-password")
};
// Deutsche IP aus Berlin
var proxyDe = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-DE-city-berlin", "your-password")
};SocketsHttpHandler: Der moderne Ansatz
Seit .NET Core 2.1 ist SocketsHttpHandler der empfohlene Handler. Er bietet mehr Kontrolle über Connection-Pooling, DNS-Refresh und Proxy-Selection.
using System.Net;
using System.Net.Http;
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password")
};
var handler = new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
// Connection-Pool-Tuning für produktive Systeme
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
MaxConnectionsPerServer = 100,
// DNS-Refresh für rotierende Proxys
EnableMultipleHttp2Connections = true,
AutomaticDecompression = DecompressionMethods.All
};
var client = new HttpClient(handler);
// Per-Request Proxy-Override über HttpRequestMessage
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.ipify.org?format=json");
// Proxy kann auch per request.Properties gesetzt werden (fortgeschritten)Warum PooledConnectionLifetime wichtig ist
Bei rotierenden Residential Proxys ändert sich die IP-Adresse regelmäßig. Wenn Sie eine Connection länger als die Rotationszeit offen halten, verwenden Sie möglicherweise eine veraltete IP. Setzen Sie PooledConnectionLifetime auf einen Wert unterhalb der Rotationszeit:
// Bei ProxyHat: typische Rotation alle 5-15 Minuten
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(3),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1)
};Polly: Retry und Circuit Breaker
Proxy-Requests scheitern häufiger als direkte Requests – Rate Limits, Timeouts, IP-Blöcke. Polly bietet eine elegante Lösung für Retry-Logik und Circuit Breaker.
using Polly;
using Polly.Retry;
using Polly.CircuitBreaker;
// Retry-Strategie: 3 Versuche mit exponentiellem Backoff
var retryStrategy = RetryStrategyBuilder<HttpResponseMessage>.Create()
.HandleResult(r => !r.IsSuccessStatusCode)
.Handle<HttpRequestException>()
.Handle<TaskCanceledException>() // Timeouts
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, delay, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {delay.TotalSeconds}s: {outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString()}");
});
// Circuit Breaker: Nach 5 konsekutiven Fehlern für 30s öffnen
var circuitBreaker = new CircuitBreakerStrategyBuilder<HttpResponseMessage>()
.HandleResult(r => !r.IsSuccessStatusCode)
.Handle<HttpRequestException>()
.Handle<TaskCanceledException>()
.AdvancedCircuitBreakerAsync(
failureThreshold: 0.5, // 50% Fehlerquote
samplingDuration: TimeSpan.FromSeconds(30),
minimumThroughput: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onOpened: args => Console.WriteLine("Circuit breaker opened"),
onClosed: args => Console.WriteLine("Circuit breaker closed"))
.Build();
// Kombinierte Strategie
var resiliencePipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddStrategy(retryStrategy)
.AddStrategy(circuitBreaker)
.Build();
// Verwendung
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password")
},
UseProxy = true
};
var client = new HttpClient(handler);
var response = await resiliencePipeline.ExecuteAsync(
async ct => await client.GetAsync("https://httpbin.org/status/500", ct),
CancellationToken.None);Parallel.ForEachAsync: Concurrent Scraping
Für produktives Web-Scraping müssen Sie mehrere URLs parallel abrufen. Parallel.ForEachAsync in .NET 8 bietet eine saubere, effiziente Lösung mit integriertem Throttling.
using System.Net;
using System.Net.Http;
var urls = new[]
{
"https://httpbin.org/ip",
"https://httpbin.org/headers",
"https://httpbin.org/user-agent",
"https://api.ipify.org?format=json",
"https://ifconfig.me/ip"
};
// Shared HttpClient (thread-safe)
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password")
};
var handler = new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
MaxConnectionsPerServer = 50, // Concurrent connections
PooledConnectionLifetime = TimeSpan.FromMinutes(3)
};
using var client = new HttpClient(handler);
// Ergebnisse sammeln
var results = new System.Collections.Concurrent.ConcurrentBag<(string Url, string Content, TimeSpan Duration)>();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Parallel mit MaxDegreeOfParallelism
await Parallel.ForEachAsync(urls, new ParallelOptions
{
MaxDegreeOfParallelism = 10 // Limit concurrent requests
}, async (url, ct) =>
{
var sw = System.Diagnostics.Stopwatch.StartNew();
try
{
var response = await client.GetAsync(url, ct);
var content = await response.Content.ReadAsStringAsync(ct);
sw.Stop();
results.Add((url, content, sw.Elapsed));
Console.WriteLine($"[{sw.ElapsedMilliseconds}ms] {url}: {content[..Math.Min(100, content.Length)]}...");
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching {url}: {ex.Message}");
}
});
stopwatch.Stop();
Console.WriteLine($"\nTotal: {stopwatch.ElapsedMilliseconds}ms for {urls.Length} URLs");
Console.WriteLine($"Average: {results.Average(r => r.Duration.TotalMilliseconds):0.00}ms per request");Rotating Proxy Pool Service mit DI
Für produktive Anwendungen benötigen Sie einen Proxy-Pool-Service, der IPs automatisch rotiert und mit Dependency Injection funktioniert.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Net;
using System.Collections.Concurrent;
// Proxy-Konfiguration
public class ProxyConfig
{
public string GatewayHost { get; set; } = "gate.proxyhat.com";
public int HttpPort { get; set; } = 8080;
public int Socks5Port { get; set; } = 1080;
public string Username { get; set; } = "user";
public string Password { get; set; } = "password";
public string[] Countries { get; set; } = ["US", "DE", "GB"];
public int RotationIntervalSeconds { get; set; } = 300; // 5 Minuten
}
// Proxy-Info
public record ProxyInfo(string SessionId, string Country, WebProxy Proxy, DateTime CreatedAt);
// Proxy-Pool Service Interface
public interface IProxyPoolService
{
Task<ProxyInfo> GetProxyAsync(string? country = null, CancellationToken ct = default);
Task ReleaseProxyAsync(ProxyInfo proxy, CancellationToken ct = default);
Task RotateAllAsync(CancellationToken ct = default);
}
// Proxy-Pool Implementation
public class ProxyPoolService : IProxyPoolService, IDisposable
{
private readonly ProxyConfig _config;
private readonly ConcurrentBag<ProxyInfo> _availableProxies = new();
private readonly ConcurrentDictionary<string, ProxyInfo> _inUseProxies = new();
private readonly Timer _rotationTimer;
private readonly Random _random = new();
public ProxyPoolService(ProxyConfig config)
{
_config = config;
// Initiale Proxies erstellen
InitializeProxies();
// Automatische Rotation
_rotationTimer = new Timer(
_ => RotateAllAsync().GetAwaiter().GetResult(),
null,
TimeSpan.FromSeconds(_config.RotationIntervalSeconds),
TimeSpan.FromSeconds(_config.RotationIntervalSeconds));
}
private void InitializeProxies()
{
foreach (var country in _config.Countries)
{
for (int i = 0; i < 5; i++) // 5 Proxies pro Land
{
var proxy = CreateProxy(country);
_availableProxies.Add(proxy);
}
}
}
private ProxyInfo CreateProxy(string country)
{
var sessionId = Guid.NewGuid().ToString("N")[..8];
var username = $"{_config.Username}-country-{country}-session-{sessionId}";
var proxy = new WebProxy($"http://{_config.GatewayHost}:{_config.HttpPort}")
{
Credentials = new NetworkCredential(username, _config.Password)
};
return new ProxyInfo(sessionId, country, proxy, DateTime.UtcNow);
}
public Task<ProxyInfo> GetProxyAsync(string? country = null, CancellationToken ct = default)
{
ProxyInfo? proxy;
if (country != null)
{
// Spezifisches Land suchen
proxy = _availableProxies.FirstOrDefault(p => p.Country == country);
if (proxy != null)
{
_availableProxies.TryTake(out _);
_inUseProxies[proxy.SessionId] = proxy;
}
}
else
{
// Beliebigen Proxy nehmen
if (_availableProxies.TryTake(out proxy!))
{
_inUseProxies[proxy.SessionId] = proxy;
}
}
if (proxy == null)
{
// Neuen Proxy erstellen falls Pool leer
proxy = CreateProxy(country ?? _config.Countries[_random.Next(_config.Countries.Length)]);
_inUseProxies[proxy.SessionId] = proxy;
}
return Task.FromResult(proxy);
}
public Task ReleaseProxyAsync(ProxyInfo proxy, CancellationToken ct = default)
{
_inUseProxies.TryRemove(proxy.SessionId, out _);
// Proxy recyceln falls nicht zu alt
if ((DateTime.UtcNow - proxy.CreatedAt).TotalMinutes < 10)
{
_availableProxies.Add(proxy);
}
return Task.CompletedTask;
}
public Task RotateAllAsync(CancellationToken ct = default)
{
Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}] Rotating proxy pool...");
// Alle in Verwendung behalten, verfügbare rotieren
var oldProxies = _availableProxies.ToArray();
_availableProxies.Clear();
foreach (var country in _config.Countries)
{
for (int i = 0; i < oldProxies.Count(p => p.Country == country); i++)
{
_availableProxies.Add(CreateProxy(country));
}
}
return Task.CompletedTask;
}
public void Dispose()
{
_rotationTimer.Dispose();
}
}
// HttpClient Factory Integration
public static class ProxyServiceExtensions
{
public static IServiceCollection AddProxyPool(
this IServiceCollection services,
Action<ProxyConfig> configure)
{
var config = new ProxyConfig();
configure(config);
services.AddSingleton(config);
services.AddSingleton<IProxyPoolService, ProxyPoolService>();
// Named HttpClient mit Proxy-Support
services.AddHttpClient("ProxiedClient")
.ConfigurePrimaryHttpMessageHandler(sp =>
{
var proxyPool = sp.GetRequiredService<IProxyPoolService>();
var proxy = proxyPool.GetProxyAsync().GetAwaiter().GetResult();
return new SocketsHttpHandler
{
Proxy = proxy.Proxy,
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(3)
};
});
return services;
}
}
// Verwendung in Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddProxyPool(config =>
{
config.Username = "user";
config.Password = "your-password";
config.Countries = ["US", "DE", "GB", "FR"];
config.RotationIntervalSeconds = 300;
});
var app = builder.Build();
// Usage
var proxyPool = app.Services.GetRequiredService<IProxyPoolService>();
var proxy = await proxyPool.GetProxyAsync("US");
Console.WriteLine($"Got proxy: Session={proxy.SessionId}, Country={proxy.Country}");TLS: Zertifikats-Pinning und Custom Root CA
Manche Proxy-Anbieter verwenden TLS-Inspection oder Custom Certificates. Hier ist, wie Sie damit umgehen.
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
// Custom Certificate Validation (für Development/Test)
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password")
},
UseProxy = true,
// TLS-Konfiguration
SslOptions = new SslClientAuthenticationOptions
{
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
// Custom Certificate Validation
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
// Für Production: Proper Certificate Pinning
if (errors == SslPolicyErrors.None)
return true;
// Custom Root CA akzeptieren (nur für bekannte Proxies!)
var expectedThumbprint = "YOUR_PROXY_CA_THUMBPRINT";
if (cert is X509Certificate2 cert2 &&
cert2.Thumbprint?.Equals(expectedThumbprint, StringComparison.OrdinalIgnoreCase) == true)
{
return true;
}
Console.WriteLine($"Certificate validation failed: {errors}");
return false; // Strict für Production
}
},
// Connection-Timeout für TLS-Handshake
ConnectTimeout = TimeSpan.FromSeconds(30)
};
var client = new HttpClient(handler);
// Certificate Pinning mit bekanntem Public Key
public class CertificatePinner
{
private static readonly HashSet<string> _allowedPublicKeys = new()
{
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Durch echten Key ersetzen
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
};
public static bool ValidateCertificate(
HttpRequestMessage request,
X509Certificate2? certificate,
X509Chain? chain,
SslPolicyErrors errors)
{
if (certificate == null) return false;
// Public Key extrahieren
var publicKey = certificate.GetPublicKeyString();
var thumbprint = certificate.Thumbprint;
// Pinning-Check
var pinnedKey = $"sha256/{Convert.ToBase64String(certificate.GetPublicKeyHash())}";
return _allowedPublicKeys.Contains(pinnedKey) || errors == SslPolicyErrors.None;
}
}
// Mit HttpClient verwenden
var secureHandler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
Credentials = new NetworkCredential("user-country-US", "your-password")
},
UseProxy = true,
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = CertificatePinner.ValidateCertificate
}
};Produktions-Tipps und Best Practices
1. HttpClient-Lebenszyklus
- Singleton Pattern: Verwenden Sie
IHttpClientFactoryoder einen Singleton-HttpClient. Pro-Request-Instanziierung führt zu Socket-Exhaustion. - PooledConnectionLifetime: Setzen Sie dies auf 3-5 Minuten bei rotierenden Proxys.
- Timeouts: Setzen Sie angemessene Timeouts (30-60 Sekunden für Proxys).
2. Proxy-Rotation
- Session-basiert: Verwenden Sie Session-IDs für Sticky Sessions (gleiche IP für mehrere Requests).
- Rotation: Rotieren Sie IPs regelmäßig, aber nicht zu häufig (Rate Limits).
- Geo-Targeting: Nutzen Sie Land/Stadt-Targeting für lokale Inhalte.
3. Error Handling
- Retry-Logic: Verwenden Sie Polly für automatische Retries mit Backoff.
- Circuit Breaker: Schützen Sie Ihre Anwendung vor kaskadierenden Fehlern.
- Logging: Loggen Sie Proxy-IP, Response-Status und Latenz für Debugging.
Proxy-Typen im Vergleich
| Proxy-Typ | Geschwindigkeit | Erkennungsrisiko | Use Case |
|---|---|---|---|
| Datacenter | Sehr schnell | Hoch | High-Volume Scraping, nicht-kritische Targets |
| Residential | Mittel | Niedrig | SERP-Scraping, E-Commerce, Social Media |
| Mobile | Variabel | Sehr niedrig | Mobile-spezifische Inhalte, Social Media |
Für C# residential proxies bietet ProxyHat eine zuverlässige Lösung mit hoher Verfügbarkeit und Geo-Targeting-Optionen.
Key Takeaways
- Verwenden Sie
SocketsHttpHandlermitPooledConnectionLifetimefür rotierende Proxys.- Integrieren Sie Polly für Retry-Logik und Circuit Breaker in produktiven Anwendungen.
- Nutzen Sie
Parallel.ForEachAsyncfür effizientes Concurrent Scraping mit Throttling.- Implementieren Sie einen Proxy-Pool-Service mit DI für saubere Architektur.
- Konfigurieren Sie TLS-Validation für Custom Root CAs und Certificate Pinning.
- Bei ProxyHat: Nutzen Sie Username-Flags für Geo-Targeting und Sticky Sessions.
Weiterführende Ressourcen
Weitere Informationen zu Proxy-Konfigurationen und Use Cases finden Sie in unserer umfassenden Proxy-Anleitung. Für spezifische Anwendungen lesen Sie unseren Guide zu Web-Scraping mit Proxys.
Die aktuellen ProxyHat-Tarife und verfügbaren Standorte finden Sie auf unserer Preisseite und der Standortübersicht.






