Bu kılavuz, .NET 8 üzerinde C# ile bir HttpClient proxy’sinin nasıl yapılandırılacağını göstermektedir: WebProxy ile NetworkCredential, HttpClientHandler ve SocketsHttpHandler arasında seçim, IHttpClientFactory ile istemcinin doğru şekilde yeniden kullanımı, zaman aşımları ve iptal, ortam değişkeni yapılandırması, atlama listeleri ve üretim ortamında gerçekten karşılaşacağınız iki istisna. Makale, kalıcı bir konut oturumu açan, çıkış IP'sini doğrulayan ve başarısızlık durumunda yeni bir oturumla yeniden deneme yapan eksiksiz bir konsol programı ile sona eriyor.
Aşağıdaki tüm içerik, üst düzey deyimlerle .NET 8'i hedefliyor; ancak API, .NET Core 3.0'dan beri kararlıdır ve aynı kod .NET 6 ve 7'de de derlenebilir.
WebProxy ve NetworkCredential
Temel yapı taşı, learn.microsoft.com adresinde belgelenen System.Net.WebProxy'dur. Bu yapı, proxy adresini ve Credentials özelliği aracılığıyla, proxy 407 hatası verdiğinde işleyicinin gönderdiği kullanıcı adı ve şifreyi içerir.
using System.Net;
var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
{
Credentials = new NetworkCredential("USERNAME", "PASSWORD")
};
Hedefler HTTPS olsa bile adres şeması http://'dur. Bir https:// URL'si için işleyici, proxy'ye bir CONNECT isteği gönderir ve hedef o tünel içindeyken TLS'yi çalıştırır; böylece proxy geçişi düz HTTP olarak kalır ve sertifika doğrulaması, proxy olmasaydı olduğu gibi tam olarak hedefe uygulanır.
Kimlik bilgileri URL'de değil, bir NetworkCredential içinde bulunduğundan, şifredeki özel karakterleri yüzde kodlamanıza gerek yoktur. Bu, kullanıcı bilgilerini yalnızca proxy URL'sinde kabul eden istemcilere kıyasla pratik bir avantajdır.
HttpClientHandler ve SocketsHttpHandler Karşılaştırması
İki işleyici sınıfı da proxy kabul eder. HttpClientHandler, uzun süredir kullanılan API’dir; .NET Core 2.1’den itibaren tüm platformlarda SocketsHttpHandler’ya devredildiğinden, proxy desteği açısından işlevsel bir fark yoktur. SocketsHttpHandler'yu doğrudan kullanmanın nedeni, sunduğu ek ayar seçenekleridir ve bunlardan biri proxy'ler açısından büyük önem taşır.
// Option A: HttpClientHandler
var handlerA = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true
};
// Option B: SocketsHttpHandler (preferred for new code)
var handlerB = new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
PooledConnectionLifetime bu ayar seçeneğidir. HttpClient, bağlantıları havuzlar ve istekler arasında yeniden kullanır. 9000 numaralı bağlantı noktasında dönen bir proxy kullanıldığında, proxy ile bağlantı kurulduğunda çıkış IP'si seçilir; aynı havuzlanmış bağlantı üzerinden gelen sonraki istekler aynı çıkış IP'sini kullanmaya devam edebilir. Havuzlanmış bağlantılara sınırlı bir ömür atamak, periyodik olarak yeniden bağlanmayı zorunlu kılar; bu da proxy sunucusundaki DNS değişikliklerini alır. İki dakika makul bir varsayılan süredir; rotasyonun, bağlantılarınızın doğal olarak değişme sıklığından daha sık gerçekleşmesini istiyorsanız bu süreyi kısaltın.
Tablo, her bir seçeneğin ne zaman uygun olduğunu özetlemektedir.
| İhtiyaç | İşleyici | Ayar |
|---|
| Minimum proxy yapılandırması, mevcut kod | HttpClientHandler | Proxy, UseProxy = true |
| Dönen IP'lerle sınırlı bağlantı yeniden kullanımı | SocketsHttpHandler | PooledConnectionLifetime |
| Proxy sunucusu başına bağlantı sınırı | SocketsHttpHandler | MaxConnectionsPerServer |
| Bir istemci için proxy'yi devre dışı bırakma | her ikisi | UseProxy = false |
Tek Bir HttpClient'ı Yeniden Kullanma
HttpClient, tek seferlik oluşturulup uygulamanın ömrü boyunca yeniden kullanılmak üzere tasarlanmıştır. Her istek için yeni bir örnek oluşturmak, yük altında soket kaynaklarını tüketir; ayrıca bir proxy kullanıldığında, kurmak için gecikme bedeli ödediğiniz havuzdaki CONNECT tünellerini de boşa harcar. Konsol uygulamasında veya işleyicide tek bir statik örnek tutun.
var http = new HttpClient(handlerB)
{
Timeout = TimeSpan.FromSeconds(30)
};
ASP.NET Core’da IHttpClientFactory
ASP.NET Core’da, adlandırılmış bir istemciyi kaydedin ve ConfigurePrimaryHttpMessageHandler aracılığıyla işleyiciyi ekleyin. Fabrika, işleyicinin ömrünü sizin için yönetir ve işleyicileri bir programa göre döndürür; bu da hem soket tükenmesi sorununu hem de güncel olmayan DNS sorununu önler.
// Program.cs
using System.Net;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("geonode", client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
{
Credentials = new NetworkCredential(
Environment.GetEnvironmentVariable("GEONODE_USER"),
Environment.GetEnvironmentVariable("GEONODE_PASS"))
};
return new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
});
var app = builder.Build();
app.MapGet("/ip", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("geonode");
return await client.GetStringAsync("https://api.ipify.org?format=json");
});
app.Run();
İstemciye ihtiyaç duyduğunuz her yere IHttpClientFactory'yu enjekte edin ve CreateClient("geonode")'yu çağırın. Geri dönen HttpClient'nun oluşturulması ucuzdur, çünkü altta yatan maliyetli işleyici paylaşılır.
Zaman Aşımları ve CancellationToken
HttpClient.Timeout, proxy’ye bağlanma, CONNECT el sıkışması, hedefle TLS ve yanıt başlıklarının okunması dahil olmak üzere bir istek için izin verilen toplam süredir. Varsayılan değer 100 saniyedir; bu, bir kazıyıcı işleyicisi için çok uzun bir süredir. Bu değeri istemcide ayarlayın ve daha hassas kontrol için her istek başına bir CancellationToken değeri aktarın.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
try
{
var body = await http.GetStringAsync("https://example.com/", cts.Token);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
// HttpClient.Timeout elapsed
}
catch (TaskCanceledException)
{
// the CancellationToken was cancelled (our 15 s budget)
}
Ev tipi çıkışlar, doğrudan bağlantıya kıyasla gecikmeye neden olur; bu nedenle istek başına birkaç saniye ek süre ayırın. Birçok istekte aynı aşamada tutarlı bir zaman aşımı, genellikle hedefin yavaş olmasından ziyade proxy sunucusuna giden ağ yolunda bir sorun olduğunu gösterir.
DefaultProxy ve ortam değişkenleri
İşleyicide Proxy ayarlanmamışsa, .NET HttpClient.DefaultProxy değerini kullanır. .NET Core 3.0 ve sonraki sürümlerde bu statik özellik, HTTP_PROXY, HTTPS_PROXY ve NO_PROXY ortam değişkenlerinden başlatılır ve URL kimlik bilgilerini içerebilir.
export HTTP_PROXY="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
export HTTPS_PROXY="http://USERNAME:PASSWORD@premium-residential.geonode.com:9000"
export NO_PROXY="localhost,127.0.0.1,.internal"
Ayrıca, başlangıçta kodda HttpClient.DefaultProxy değerini atayabilirsiniz; bu, Proxy'yu geçersiz kılan her işleyiciyi etkiler. Bu, kapsayıcılarda kullanışlıdır, ancak küresel bir durumdur: NO_PROXY bu ana bilgisayarları listelemediği sürece, bir durum kontrol istemcisi veya dahili bir API çağrısı da konut proxy'sinden geçecektir. Tek kullanımlık bir komut dosyasının ötesindeki her şey için, adlandırılmış bir işleyicide açıkça belirtilen bir WebProxy daha nettir.
BypassList ve BypassProxyOnLocal
WebProxy'nun kendine özgü bir hariç tutma mekanizması vardır. BypassProxyOnLocal = true, adında nokta bulunmayan ana bilgisayarlar için (localhost veya sadece makine adı gibi) proxy'yi atlar. BypassList, hedef URI ile eşleşen düzenli ifadeleri kabul eder.
var proxy = new WebProxy("http://premium-residential.geonode.com:9000")
{
Credentials = new NetworkCredential("USERNAME", "PASSWORD"),
BypassProxyOnLocal = true,
BypassList = new[] { @"^https?://(.*\.)?internal\.example(:\d+)?/" }
};
Bunların glob desenleri değil, düzenli ifade desenleri olduğunu unutmayın; kaçış karakteri eklenmemiş bir nokta, herhangi bir karakterle eşleşir.
407 ve diğer hata durumlarıyla başa çıkma
Hemen hemen her HttpClient proxy biletinde iki hata türü görülür. İlki, 407 Proxy Authentication Required (Proxy Kimlik Doğrulaması Gerekiyor) hatasıdır; bu, proxy’nin herhangi bir veriyi iletmeden önce kimlik bilgilerini reddettiği anlamına gelir. Bu hata genellikle HttpRequestException kodu olarak görünür; ancak .NET sürümüne ve hatanın kaynağına bağlı olarak, durum kodu HttpStatusCode.ProxyAuthenticationRequired olan bir yanıt da alabilirsiniz. Her ikisini de kontrol edin.
using System.Net;
using System.Net.Http;
try
{
using var response = await http.GetAsync("https://example.com/");
if (response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
throw new InvalidOperationException("proxy rejected credentials (407)");
}
response.EnsureSuccessStatusCode();
var html = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
// Also 407: fix the username grammar or password; retrying will not help.
}
catch (HttpRequestException ex)
{
// Connection refused, DNS failure, TLS error, or EnsureSuccessStatusCode on 4xx/5xx.
Console.Error.WriteLine($"request failed: {ex.Message} (status {ex.StatusCode})");
}
İkincisi, yukarıda ele alınan zaman aşımları için verilen TaskCanceledException kodudur. Her ikisi için de geçerli olan önemli kural şudur: 407 hatası bir yapılandırma hatasıdır ve yeniden denememelisiniz; zaman aşımı veya hedef sunucudan gelen 5xx hatası geçicidir ve yeni bir oturumda yeniden deneme için makul bir adaydır.
Kullanıcı adı üzerinden sabit oturumlar
9000 numaralı bağlantı noktası, her yeni bağlantı için yeni bir çıkış IP’si verir. Bir akışta tek bir IP’nin birden fazla istek için kullanılması gerektiğinde (giriş işleminin ardından veri sayfaları veya sayfalandırılmış sonuçlar gibi), 10000 numaralı bağlantı noktasını kullanın ve oturumu kullanıcı adı üzerinden seçin. Dilbilgisi, temel kullanıcı adınıza son ekler ekler: coğrafi hedefleme için -country-xx, 8 karakterlik alfasayısal bir kimlik içeren -session-XXXXXXXX ve isteğe bağlı olarak 180 ile 86400 arasında bir değer içeren -lifetime-SECONDS.
static string NewSessionId() =>
Guid.NewGuid().ToString("N")[..8]; // 8 alphanumeric chars
static WebProxy StickyProxy(string baseUser, string pass, string country = "us", int lifetime = 600)
{
var user = $"{baseUser}-country-{country}-session-{NewSessionId()}-lifetime-{lifetime}";
return new WebProxy("http://premium-residential.geonode.com:10000")
{
Credentials = new NetworkCredential(user, pass)
};
}
Bu proxy üzerine kurulu bir istemci aracılığıyla yapılan her istek, on dakika boyunca aynı çıkış IP'sini paylaşır. IP'yi değiştirmek için yeni bir oturum kimliği ile yeni bir işleyici oluşturun. Oturum, kimlik bilgilerine bağlı olduğundan, aynı kullanıcı adına sahip iki istemci, işlemler arasında bile aynı çıkış IP'sini paylaşır.
Çıkış IP'sini Doğrulama
HttpClient proxy'sinin kullanıldığını asla varsaymayın; bunu doğrulayın. İstemci üzerinden https://api.ipify.org?format=json (veya https://httpbin.org/ip) isteği gönderin ve bunu gerçek adresinizle karşılaştırın. Kalıcı oturumda iki çağrı da aynı IP adresini döndürür; 9000 numaralı bağlantı noktası ve yeni bir bağlantı kullanıldığında ise farklılık gösterir.
using System.Text.Json;
var json = await http.GetStringAsync("https://api.ipify.org?format=json");
var ip = JsonDocument.Parse(json).RootElement.GetProperty("ip").GetString();
Console.WriteLine($"exit IP: {ip}");
Tam konsol örneği (.NET 8)
Aşağıdaki program her şeyi bir araya getirir: kalıcı oturum, sınırlı zaman aşımları, 407 hatası algılama ve geçici hatalarda yeni bir oturumla en fazla üç deneme. dotnet new console -n ProxyDemo ile oluşturun, Program.cs'yu değiştirin, GEONODE_USER ve GEONODE_PASS'yu ayarlayın ve dotnet run'yu çalıştırın.
// Program.cs (.NET 8, top-level statements)
using System.Net;
using System.Text.Json;
var baseUser = Environment.GetEnvironmentVariable("GEONODE_USER")
?? throw new InvalidOperationException("GEONODE_USER not set");
var password = Environment.GetEnvironmentVariable("GEONODE_PASS")
?? throw new InvalidOperationException("GEONODE_PASS not set");
var target = args.Length > 0 ? args[0] : "https://api.ipify.org?format=json";
for (var attempt = 1; attempt <= 3; attempt++)
{
var sessionId = Guid.NewGuid().ToString("N")[..8];
var user = $"{baseUser}-country-us-session-{sessionId}-lifetime-600";
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://premium-residential.geonode.com:10000")
{
Credentials = new NetworkCredential(user, password)
},
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
using var http = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
try
{
// 1. Confirm the exit IP for this session.
var ipJson = await http.GetStringAsync("https://api.ipify.org?format=json", cts.Token);
var ip = JsonDocument.Parse(ipJson).RootElement.GetProperty("ip").GetString();
Console.WriteLine($"[attempt {attempt}] session {sessionId} exit IP {ip}");
// 2. Fetch the real target on the same IP.
using var response = await http.GetAsync(target, cts.Token);
if (response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
Console.Error.WriteLine("407: proxy rejected credentials, not retrying");
return;
}
if ((int)response.StatusCode is 403 or 429 or >= 500)
{
Console.Error.WriteLine($"[attempt {attempt}] got {(int)response.StatusCode}, rotating session");
continue;
}
var body = await response.Content.ReadAsStringAsync(cts.Token);
Console.WriteLine($"{(int)response.StatusCode} {target} ({body.Length} chars)");
return;
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.ProxyAuthenticationRequired)
{
Console.Error.WriteLine("407: proxy rejected credentials, not retrying");
return;
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"[attempt {attempt}] network error: {ex.Message}");
}
catch (TaskCanceledException)
{
Console.Error.WriteLine($"[attempt {attempt}] timed out");
}
await Task.Delay(TimeSpan.FromMilliseconds(500 * attempt));
}
Console.Error.WriteLine("gave up after 3 attempts");
İşleyici, her deneme için kasıtlı olarak yeniden oluşturulur: yapışkan oturum, kimlik bilgilerindeki kullanıcı adına bağlıdır; bu nedenle yeni bir oturum için yeni bir WebProxy gerekir. Uzun süreli çalışan bir hizmette, istek başına değil, oturum kimliği başına bir istemciyi önbelleğe alırsınız.
Sık yapılan hatalar
Her istek için yeni bir HttpClient oluşturmak
Her yeni istemci yeni bir CONNECT tüneli açar ve TIME_WAIT’da soketler bırakır. Tek bir örneği yeniden kullanın ya da IHttpClientFactory’yu kullanın ve rotasyonu PooledConnectionLifetime’ya bırakın.
https:// proxy adresini kullanma
Proxy atlaması düz HTTP'dir; HTTPS hedefleri CONNECT tüneli içinde iletilir. WebProxy adresinde bir https:// şeması, .NET'e proxy ile TLS müzakeresi yapmasını söyler; bu da düz HTTP bekleyen bir uç noktada hata verir.
407 Hatasını Yeniden Deneme
407 hatası, proxy'nin kimlik bilgilerini kabul etmediği anlamına gelir. Aynı kullanıcı adı ve şifreyle yeniden denemek sadece zaman kaybıdır. Hemen başarısızlığı kabul edin ve kullanıcı adı biçimini, özellikle oturum kimliği uzunluğunu ve geçerlilik süresini kontrol edin.
NO_PROXY ve BypassList'i Yoksayma
Proxy DefaultProxy adresinden geliyorsa, sağlık kontrolleri ve dahili API'ler dahil olmak üzere işlemdeki tüm giden çağrılar bu proxy'yi kullanır. Bu ana bilgisayarları NO_PROXY veya BypassList adreslerinde listeleyin ya da proxy'yi yalnızca belirli bir istemcide yapılandırın.
Sonraki adım
Yukarıdaki kod, Geonode konut proxy'lerinde değiştirilmeden çalışır: 195'ten fazla ülkede 2,5 milyon çevrimiçi IP, HTTP, HTTPS ve tek bir ana bilgisayarda SOCKS5, ayrıca burada gösterilen kullanıcı adı kuralı aracılığıyla seçilen 24 saate kadar kalıcı oturumlar. Ülke, eyalet, şehir ve ASN hedeflemeleri aynı son ekleri kullanır; bu nedenle çıkış konumunu daraltmak, kod değişikliği değil, sadece bir dize değişikliği gerektirir. Fiyatlandırma, sözleşmesiz pay-as-you-go planında, aylık 7,92 $'dan başlayan 10 GB'lık paketler ve 5 $ karşılığında 3 günlük deneme sürümü şeklindedir. Ayrıntılar için ev tipi proxy'lere bakın veya fiyatlandırma sayfasında paketleri karşılaştırın. İş akışının geri kalanı için web kazıma kullanım örneğiyle başlayın.
SSS
C#'da kullanıcı adı ve şifre içeren bir HttpClient proxy'si nasıl ayarlanır?
Proxy adresini içeren bir WebProxy oluşturun ve bunun Credentials özelliğini, kullanıcı adı ve şifreyi barındıran bir NetworkCredential olarak ayarlayın. Bu WebProxy nesnesini bir HttpClientHandler veya SocketsHttpHandler nesnesinin Proxy özelliğine atayın, UseProxy'yu true olarak ayarlayın ve işleyiciyi HttpClient yapıcıya aktarın. Proxy 407 yanıtını verdiğinde işleyici kimlik bilgilerini gönderir.
Proxy için HttpClientHandler mi yoksa SocketsHttpHandler mi kullanmalıyım?
.NET Core 2.1 ve sonraki sürümlerde HttpClientHandler, SocketsHttpHandler üzerinde ince bir sarmalayıcıdır, bu nedenle her ikisi de çalışır. SocketsHttpHandler, havuzlanmış bağlantıları periyodik olarak geri dönüştürmenize olanak tanıyan PooledConnectionLifetime'yi kullanıma sunar. Bu, dönen proxy'lerde önemlidir; çünkü yeniden kullanılan bir bağlantı, kapanana kadar aynı çıkış IP'sini korur. Yeni kodlar için SocketsHttpHandler'yu tercih edin.
HttpClient proxy isteğim neden TaskCanceledException istisnası atıyor?
HttpClient, zaman aşımını özel bir zaman aşımı istisnası olarak değil, TaskCanceledException olarak bildirir. Bu istisna, HttpClient.Timeout süresi dolduğunda veya ilettiğiniz CancellationToken iptal edildiğinde atılır. .NET 5 ve sonraki sürümlerde, nedenin istemci zaman aşımı olması durumunda iç istisna TimeoutException olur; bu sayede ikisini birbirinden ayırt edebilirsiniz.
HttpClient, HTTP_PROXY ve HTTPS_PROXY ortam değişkenlerini okur mu?
Evet. .NET Core 3.0 ve sonraki sürümlerde, HttpClient.DefaultProxy, HTTP_PROXY, HTTPS_PROXY ve NO_PROXY değerlerinden başlatılır ve işleyiciler, açık bir Proxy ayarlanmadığında bunu kullanır. Kimlik bilgileri URL'ye eklenebilir. Ortam değişkenlerini ayarlamak, kapsayıcılarda kullanışlıdır; ancak kodda açık bir şekilde WebProxy belirtilmesi, mantıksal olarak daha kolay anlaşılır.
Birkaç HttpClient proxy isteğinde aynı IP adresini nasıl koruyabilirim?
10000 numaralı sabit bağlantı noktasını kullanın ve kullanıcı adına bir oturum soneki ekleyin; örneğin, USER-country-us-session-a1b2c3d4-lifetime-600. Bu kullanıcı adıyla gönderilen tüm istekler, 180 ile 86400 saniye arasında belirtilen süre boyunca tek bir çıkış IP'sini paylaşır. Yeni bir IP istiyorsanız, 8 karakterlik yeni bir oturum kimliği oluşturun.
Ev tipi bir HttpClient proxy'si üzerinden veri toplama yaparken hangi sınırlamalar geçerlidir?
Bant genişliği gigabayt başına faturalandırılır; bu nedenle yalnızca ihtiyacınız olanı indirin ve mümkün olduğunca resimlerden kaçının. Sabit oturumlar bir IP adresini en fazla 24 saat boyunca tutar ve cihaz çevrimdışı olursa daha erken sona erebilir; bu nedenle kodunuz IP değişikliğini yönetebilmelidir. Proxy kullanımı, sizi bir sitenin hizmet şartlarından, robot yönergelerinden veya hız sınırlamalarından muaf tutmaz.