Rust ile HTTP Proxy Kullanımı: reqwest, hyper ve Dönen Proxy Havuzu

Rust'ta yüksek performanslı scraping için HTTP proxy yapılandırmasını reqwest ve hyper ile öğrenin; dönen proxy havuzu, hata yönetimi, TLS ve eşzamanlılık desenlerini kod örnekleriyle inceleyin.

Rust ile HTTP Proxy Kullanımı: reqwest, hyper ve Dönen Proxy Havuzu

Rust ile scraping altyapısı kuruyorsanız, en büyük sorun hız değil — kararlılıktır. Tek bir IP'den binlerce istek attığınızda rate-limit, CAPTCHA ve bloklar anında devreye girer. Çözüm: Rust HTTP proxy desteğini doğru yapılandırmak ve residential proxy'lerle dönen bir IP havuzu oluşturmak.

Bu rehberde reqwest'ten hyper'a, tokio + JoinSet eşzamanlılık deseninden thiserror ile hata yönetimine kadar Rust ekosisteminde proxy kullanımını çalıştırılabilir kod örnekleriyle ele alıyoruz. ProxyHat residential proxy'lerini kullanarak tüm örnekleri canlı ortamda test edebilirsiniz.

reqwest ile Temel Proxy Yapılandırması

reqwest, Rust'ın en yaygın HTTP istemcisi. Proxy desteği varsayılan olarak gelir — Proxy::all() ile tüm trafiği, Proxy::http() ile yalnızca HTTP trafiğini yönlendirebilirsiniz.

use reqwest::Proxy;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // ProxyHat HTTP proxy'si — tüm trafiği yönlendir
    let proxy = Proxy::all("http://user-country-US:pass@gate.proxyhat.com:8080")?
        .no_proxy("localhost,127.0.0.1"); // Dahili adresler proxy'den geçmesin

    let client = reqwest::Client::builder()
        .proxy(proxy)
        .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
        .timeout(std::time::Duration::from_secs(30))
        .build()?;

    let resp = client.get("https://httpbin.org/ip").send().await?;
    let body = resp.text().await?;
    println!("Proxy üzerinden IP: {}", body);
    Ok(())
}

Burada iki kritik nokta var: Proxy::all() hem HTTP hem HTTPS trafiğini yakalar; .no_proxy() ise dahili servis çağrılarınızın proxy'den geçmesini engeller. ProxyHat kullanıcı adında country-US gibi geo-targeting bayrakları kullanarak ABD çıkışlı bir residential IP alabilirsiniz.

Kimlik Doğrulama ve Geo-Targeting

ProxyHat'ta kimlik doğrulama URL içine gömülüdür. Kullanıcı adı alanında şehir seviyesinde hedefleme ve sticky session da yapabilirsiniz:

// Şehir seviyesinde hedefleme
// "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"

// Sticky session (aynı IP'yi tut)
// "http://user-session-abc123:pass@gate.proxyhat.com:8080"

// SOCKS5 proxy
// "socks5://user-country-GB:pass@gate.proxyhat.com:1080"

use reqwest::Proxy;

fn build_geo_proxy(country: &str, city: Option<&str>) -> Result<Proxy, reqwest::Error> {
    let auth = match city {
        Some(c) => format!("user-country-{}-city-{}:pass", country, c.to_lowercase()),
        None => format!("user-country-{}:pass", country),
    };
    Proxy::all(format!("http://{}@gate.proxyhat.com:8080", auth))
}

hyper ile Düşük Seviye Proxy Desteği

hyper, reqwest'in alt katmanıdır. Daha fazla kontrol istiyorsanız — örneğin özel header enjeksiyonu, manuel CONNECT tüneli — hyper ile doğrudan çalışabilirsiniz. HTTPS üzerinden HTTP proxy kullanmak için CONNECT yöntemini anlamak kritiktir.

use hyper::{Client, Request, Method, Uri, body::Buf};
use hyper::client::connect::proxy::ProxyConnector;
use hyper_proxy::{Proxy, ProxyConnector as ProxyConn};
use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::Client as LegacyClient;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // HTTP proxy connector oluştur
    let proxy_uri: Uri = "http://user-country-FR:pass@gate.proxyhat.com:8080"
        .parse()?;
    let proxy = Proxy::new(hyper_proxy::Intercept::All, proxy_uri);

    // rustls tabanlı HTTPS connector
    let https = HttpsConnector::builder()
        .with_webpki_roots()?
        .https_only(true)
        .build();

    let proxy_connector = ProxyConn::from_proxy(https, proxy)?;
    let client: LegacyClient<_, hyper::Body> = LegacyClient::builder(TokioExecutor)
        .build(proxy_connector);

    let req = Request::builder()
        .method(Method::GET)
        .uri("https://httpbin.org/ip")
        .body(hyper::Body::empty())?;

    let resp = client.request(req).await?;
    let body_bytes = hyper::body::to_bytes(resp.into_body()).await?;
    println!("Yanıt: {}", String::from_utf8_lossy(body_bytes.chunk()));
    Ok(())
}

hyper ile çalışırken hyper-proxy crate'i CONNECT tünellerini otomatik yönetir. HTTPS hedefine giderken proxy önce bir CONNECT isteği gönderir, sonra TLS el sıkışmasını proxy üzerinden yapar. Bu, residential proxy'lerin güvenli sitelere erişimi için zorunludur.

tokio + JoinSet ile Eşzamanlı Scraping

Tek istek atmak scraping değil. Gerçek dünyada yüzlerce sayfayı paralel çekmeniz gerekir. tokio::task::JoinSet, iptal edilebilir ve yönetilebilir eşzamanlı görevler için idealdir.

use reqwest::Client;
use std::sync::Arc;
use tokio::task::JoinSet;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Her istekte farklı ülke IP'si kullan
    let countries = ["US", "DE", "GB", "FR", "JP"];
    let urls = vec![
        "https://httpbin.org/ip",
        "https://httpbin.org/headers",
        "https://httpbin.org/user-agent",
        "https://httpbin.org/get",
        "https://httpbin.org/json",
    ];

    let mut tasks: JoinSet<Result<String, reqwest::Error>> = JoinSet::new();

    for (i, url) in urls.iter().enumerate() {
        let country = countries[i % countries.len()];
        let url = url.to_string();

        tasks.spawn(async move {
            let proxy_url = format!(
                "http://user-country-{}:pass@gate.proxyhat.com:8080",
                country
            );
            let proxy = reqwest::Proxy::all(&proxy_url)?;
            let client = reqwest::Client::builder()
                .proxy(proxy)
                .timeout(Duration::from_secs(20))
                .build()?;

            let resp = client.get(&url).send().await?;
            resp.text().await
        });
    }

    // Tüm görevleri topla — hata olsa bile devam et
    while let Some(result) = tasks.join_next().await {
        match result {
            Ok(Ok(body)) => println!("✓ İstek başarılı (ilk 80 karakter): {:.80}", body),
            Ok(Err(e)) => eprintln!("✗ HTTP hatası: {}", e),
            Err(e) => eprintln!("✗ Görev hatası: {}", e),
        }
    }

    Ok(())
}
Performans ipucu: Her görev için yeni Client oluşturmak pahalıdır. Üretimde client'ları bir Arc ile paylaşın veya bir proxy havuzu soyutlaması kullanın (aşağıda).

Dönen Proxy Havuzu: Trait Tabanlı Soyutlama

Farklı proxy sağlayıcıları, farklı döndürme stratejileri… Bunları bir trait altında birleştirmek, test edilebilirlik ve değiştirilebilirlik açısından kritik. Rust residential proxies kullanırken her istekte IP döndürmek, rate-limit'e karşı en etkili stratejidir.

use async_trait::async_trait;
use reqwest::{Client, Proxy, RequestBuilder};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

/// Proxy havuzu soyutlaması
#[async_trait]
pub trait ProxyPool: Send + Sync {
    /// Bir sonraki proxy URL'sini döndür
    async fn next_proxy_url(&self) -> String;
    /// Proxy'li bir istemci döndür
    async fn client(&self) -> Result<Client, reqwest::Error>;
}

/// ProxyHat residential proxy havuzu — her istekte farklı IP
pub struct RotatingProxyPool {
    countries: Vec<String>,
    counter: AtomicUsize,
    base_user: String,
    password: String,
}

impl RotatingProxyPool {
    pub fn new(username: &str, password: &str, countries: Vec<&str>) -> Self {
        Self {
            countries: countries.iter().map(|s| s.to_string()).collect(),
            counter: AtomicUsize::new(0),
            base_user: username.to_string(),
            password: password.to_string(),
        }
    }
}

#[async_trait]
impl ProxyPool for RotatingProxyPool {
    async fn next_proxy_url(&self) -> String {
        let idx = self.counter.fetch_add(1, Ordering::Relaxed);
        let country = &self.countries[idx % self.countries.len()];
        let session = format!("sess-{}", idx);
        format!(
            "http://{}-country-{}-session-{}:{}@gate.proxyhat.com:8080",
            self.base_user, country, session, self.password
        )
    }

    async fn client(&self) -> Result<Client, reqwest::Error> {
        let proxy_url = self.next_proxy_url().await;
        let proxy = Proxy::all(&proxy_url)?;
        Client::builder()
            .proxy(proxy)
            .timeout(std::time::Duration::from_secs(30))
            .build()
    }
}

// Kullanım
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pool = Arc::new(RotatingProxyPool::new(
        "user", "pass", vec!["US", "DE", "JP", "GB"]
    ));

    let targets = vec!["https://httpbin.org/ip"; 8];
    let mut handles = Vec::new();

    for url in targets {
        let pool = Arc::clone(&pool);
        let url = url.to_string();
        handles.push(tokio::spawn(async move {
            let client = pool.client().await?;
            let resp = client.get(&url).send().await?;
            let body = resp.text().await?;
            println!("IP: {}", body.lines().next().unwrap_or("?"));
            Ok::<(), reqwest::Error>(())
        }));
    }

    for h in handles {
        let _ = h.await;
    }
    Ok(())
}

thiserror ile Hata Yönetimi

Scraping'de her şey yanlış gider — proxy zaman aşımı, CAPTCHA, DNS hatası, TLS el sıkışma başarısızlığı. thiserror ile tip güvenli hata tipleri oluşturmak, unwrap() paniklerinden kurtulmanın en temiz yoludur.

use thiserror::Error;
use reqwest::StatusCode;

#[derive(Error, Debug)]
pub enum ScrapingError {
    #[error("Proxy bağlantı hatası: {0}")]
    ProxyConnection(String),

    #[error("HTTP {status} — {url}")]
    Http {
        status: StatusCode,
        url: String,
    },

    #[error("Rate limit aşıldı — {retry_after:?} sonra tekrar deneyin")]
    RateLimited {
        retry_after: Option<std::time::Duration>,
    },

    #[error("CAPTCHA tespit edildi — {domain}")]
    Captcha { domain: String },

    #[error("Zaman aşımı: {0}")]
    Timeout(String),

    #[error("İstek hatası: {0}")]
    Reqwest(#[from] reqwest::Error),
}

/// Yanıtı kontrol et ve iş hatası fırlat
pub fn check_response(resp: &reqwest::Response) -> Result<(), ScrapingError> {
    let status = resp.status();
    let url = resp.url().to_string();

    if status == StatusCode::TOO_MANY_REQUESTS {
        let retry_after = resp.headers()
            .get("retry-after")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse::<u64>().ok())
            .map(std::time::Duration::from_secs);
        return Err(ScrapingError::RateLimited { retry_after });
    }

    if status.is_server_error() || status == StatusCode::FORBIDDEN {
        // Basit CAPTCHA tespiti — gerçek projede body'yi de kontrol edin
        if status == StatusCode::FORBIDDEN {
            let domain = resp.url().host_str().unwrap_or("unknown").to_string();
            return Err(ScrapingError::Captcha { domain });
        }
        return Err(ScrapingError::Http { status, url });
    }

    Ok(())
}

/// Retry mantığı ile güvenli istek
pub async fn fetch_with_retry(
    client: &reqwest::Client,
    url: &str,
    max_retries: u32,
) -> Result<String, ScrapingError> {
    let mut attempt = 0;
    loop {
        attempt += 1;
        match client.get(url).send().await {
            Ok(resp) => {
                check_response(&resp)?;
                return resp.text().await.map_err(ScrapingError::from);
            }
            Err(e) if e.is_timeout() && attempt <= max_retries => {
                let delay = std::time::Duration::from_millis(500 * attempt as u64);
                tokio::time::sleep(delay).await;
                continue;
            }
            Err(e) if e.is_connect() && attempt <= max_retries => {
                // Proxy bağlantı hatası — yeni IP ile tekrar dene
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                continue;
            }
            Err(e) => return Err(ScrapingError::from(e)),
        }
    }
}

TLS: rustls vs native-tls

Rust ekosisteminde iki büyük TLS arka ucu var. Seçiminiz derleme zamanı özellik bayraklarıyla (feature flags) belirlenir:

Özellikrustlsnative-tls
Arka uçPure Rust (ring / aws-lc-rs)OpenSSL / SChannel / Secure Transport
Cross-compileKolay — C bağımlılığı yokZor — OpenSSL bağlantısı gerekir
PerformansYüksek (özellikle aws-lc-rs ile)Platforma göre değişir
Sertifika deposuwebpki-roots (Mozilla bundle)İşletim sistemi deposu
Proxy uyumluluğuCONNECT tüneli desteklenirCONNECT tüneli desteklenir
BoyutDaha küçük binaryDaha büyük (OpenSSL link)

Öneri: Docker'da cross-compile yapıyorsanız rustls seçin. Kurumsal ortamda işletim sistemi sertifika deposu gerekiyorsa native-tls.

reqwest Cargo.toml Yapılandırması

# rustls ile (varsayılan reqwest davranışı — 0.12+)
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [
    "rustls-tls",
    "charset",
    "http2",
    "mac-auth-system",  # macOS
] }

# native-tls ile
[dependencies]
reqwest = { version = "0.12", default-features = false, features = [
    "native-tls",
    "charset",
    "http2",
] }

# SOCKS5 proxy desteği de eklemek isterseniz
reqwest = { version = "0.12", features = ["socks"] }

Derleme Zamanı Özellik Bayrakları ile Proxy Desteği

Üretimde her zaman proxy'ye ihtiyaç olmayabilir — örneğin unit testlerde veya dahili API'lerde. Feature flag'ler ile derleme zamanında proxy desteğini açıp kapatabilirsiniz:

# Cargo.toml
[features]
default = ["proxy-support"]
proxy-support = ["reqwest/proxy"]  # reqwest 0.12'de proxy opsiyonel
socks-proxy = ["reqwest/socks"]

# src/lib.rs
#[cfg(feature = "proxy-support")]
pub fn create_client(proxy_url: &str) -> Result<reqwest::Client, reqwest::Error> {
    let proxy = reqwest::Proxy::all(proxy_url)?;
    reqwest::Client::builder()
        .proxy(proxy)
        .build()
}

#[cfg(not(feature = "proxy-support"))]
pub fn create_client(_proxy_url: &str) -> Result<reqwest::Client, reqwest::Error> {
    // Proxy desteği yok — doğrudan bağlan
    reqwest::Client::builder().build()
}

// Test ederken proxy'siz derleme:
// cargo test --no-default-features

Bu yaklaşım CI/CD pipeline'larınızda proxy bağımlılığını ortadan kaldırır ve binary boyutunu küçültür. Rust HTTP proxy desteğini koşullu olarak eklemek, mikro-servis mimarilerinde özellikle değerlidir.

Üretim İçin En İyi Uygulamalar

  • Client'ı yeniden kullanın: Her istekte Client::new() oluşturmak bağlantı havuzunu sıfırlar. Arc<Client> ile paylaşın.
  • Timeout belirleyin: Residential proxy'lerde ortalama latency 1-3 saniyedir. 30 saniye üst limit, 10 saniye bağlantı timeout iyi başlangıçtır.
  • Retry ile exponential backoff: fetch_with_retry örneğindeki gibi bağlantı hatalarında artan bekleme süresiyle tekrar deneyin.
  • Rate-limit'e saygı: Aynı anda 500+ istek atmak proxy sağlayıcının rate-limit'ini tetikleyebilir. tokio::sync::Semaphore ile concurrency'yi sınırlayın.
  • Etik kurallar: robots.txt'i kontrol edin, ToS ihlali yapmayın, GDPR/CCPA kapsamındaki kişisel verileri toplayacaksanız hukuki danışmanlık alın.

Hangi Proxy Türü Ne Zaman?

SenaryoProxy TürüNeden
SERP scrapingResidential + geo-targetingArama motorları datacenter IP'leri bloklar
Fiyat karşılaştırmaResidential (dönen)E-ticaret siteleri rate-limit uygular
API proxy (hızlı)DatacenterDüşük latency, blok riski yok
Sosyal medya araştırmasıMobile proxyPlatformlar mobil IP'lere daha az şüphelenir
Sneaker/ticketingResidential + sticky sessionOturum tutarlılığı gerekir

ProxyHat, tüm bu senaryoları 70+ lokasyonda residential, mobile ve datacenter proxy'lerle karşılıyor. Detaylı fiyatlandırma için pricing sayfasını inceleyin.

Key Takeaways

  • reqwest ile proxy yapılandırmak tek satırlık iş — Proxy::all() tüm trafiği, Proxy::http() yalnızca HTTP'yi yönlendirir.
  • hyper ile CONNECT tüneli manuel kontrol edebilir, düşük seviye ihtiyaçlarınızı karşılayabilirsiniz.
  • tokio::task::JoinSet ile yüzlerce proxy'li isteği eşzamanlı ve yönetilebilir şekilde çalıştırın.
  • Rotating proxy havuzu trait'i, farklı sağlayıcı ve stratejileri değiştirilebilir kılar — test edilebilirlik için kritik.
  • thiserror ile tip güvenli hata yönetimi, unwrap() paniklerinden kurtarır ve retry mantığını temizler.
  • rustls cross-compile ve küçük binary için; native-tls işletim sistemi sertifika deposu için.
  • Feature flags ile proxy desteğini derleme zamanında açıp kapatın — CI ve test süreçlerinizi hızlandırır.

Rust ile yüksek performanslı scraping altyapısı kurmak istiyorsanız, web scraping kullanım senaryolarımızı ve SERP tracking çözümümüzü inceleyin. ProxyHat residential proxy'leri ile Rust HTTP proxy entegrasyonunuzu bugün başlatın.

Başlamaya hazır mısınız?

148+ ülkede 50M+ konut IP'sine AI destekli filtreleme ile erişin.

Fiyatlandırmayı GörüntüleKonut Proxy'leri
← Bloga Dön