Go'da proxy kullanmak, bir http.Client nesnesinin http.Transport yapısını bir Proxy fonksiyonuyla yapılandırmayı gerektirir; statik proxy'ler için genellikle http.ProxyURL, dinamik proxy seçimi için ise özel bir fonksiyon kullanılır.
Go'da neden proxy kullanılır?
Proxy'ler ağ iletişiminde, özellikle Go uygulamalarından HTTP istekleri yaparken çeşitli amaçlara hizmet eder:
- Anonimlik: İstemcinin gerçek IP adresini gizlemek.
- Coğrafi hedefleme: İstekleri o bölgelerdeki proxy'ler üzerinden yönlendirerek belirli coğrafi bölgelerle sınırlı içerik veya hizmetlere erişmek.
- İstek limitleri: Hedef sunucuların uyguladığı hız limitlerine takılmamak için istekleri birden fazla IP adresine dağıtmak.
- Web scraping: IP adreslerini döndürerek, tespiti aşarak ve istek hacmini yöneterek büyük ölçekli veri toplamayı mümkün kılmak.
- Güvenlik ve filtreleme: Güvenlik taraması, içerik filtreleme veya erişim denetimi için trafiği kurumsal proxy'ler üzerinden yönlendirmek.
- Hata ayıklama: Trafiği yakalamak ve incelemek.
Bir HTTP istemcisini proxy ile yapılandırma
Go standart kütüphanesindeki net/http paketi, proxy yapılandırmaları için güçlü destek sunar. Temel mekanizma, bir http.Client ile ilişkili http.Transport yapısını değiştirmektir.
http.ProxyFromEnvironment kullanımı
Proxy kullanmanın en basit yolu ortam değişkenlerinden yararlanmaktır. Go'nun http.ProxyFromEnvironment fonksiyonu HTTP_PROXY, HTTPS_PROXY ve NO_PROXY ortam değişkenlerini kontrol eder.
HTTP_PROXY:http://istekleri için kullanılır.HTTPS_PROXY:https://istekleri için kullanılır.NO_PROXY: Proxy'yi atlaması gereken, virgülle ayrılmış hostname listesi.
package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
// Set environment variables (for demonstration)
os.Setenv("HTTP_PROXY", "http://your_proxy_ip:port")
os.Setenv("HTTPS_PROXY", "http://your_proxy_ip:port") // Note: HTTPS_PROXY can be an HTTP proxy
// os.Setenv("NO_PROXY", "localhost,127.0.0.1")
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
Timeout: 10 * time.Second,
}
resp, err := client.Get("http://httpbin.org/ip")
if err != nil {
fmt.Printf("Error making request: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
return
}
fmt.Printf("Response from httpbin.org/ip:\n%s\n", body)
os.Unsetenv("HTTP_PROXY")
os.Unsetenv("HTTPS_PROXY")
}
Statik proxy'ler için http.ProxyURL kullanımı
Açık proxy yapılandırması için http.ProxyURL kullanılır. Bu fonksiyon, proxy adresini temsil eden bir *url.URL alır.
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"time"
)
func main() {
proxyStr := "http://your_proxy_ip:port" // Replace with your proxy address
proxyURL, err := url.Parse(proxyStr)
if err != nil {
fmt.Printf("Error parsing proxy URL: %v\n", err)
return
}
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 10 * time.Second,
}
resp, err := client.Get("http://httpbin.org/ip")
if err != nil {
fmt.Printf("Error making request: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
return
}
fmt.Printf("Response from httpbin.org/ip:\n%s\n", body)
}
Proxy kimlik doğrulaması
Birçok proxy kimlik doğrulaması ister. Basic Authentication kullanan HTTP/HTTPS proxy'lerinde kimlik bilgileri doğrudan proxy URL'sine gömülebilir:
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"time"
)
func main() {
// Format: "http://username:password@proxy_ip:port"
proxyStr := "http://user:password@your_auth_proxy_ip:port" // Replace with your authenticated proxy
proxyURL, err := url.Parse(proxyStr)
if err != nil {
fmt.Printf("Error parsing proxy URL: %v\n", err)
return
}
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 10 * time.Second,
}
resp, err := client.Get("http://httpbin.org/ip")
if err != nil {
fmt.Printf("Error making request with authenticated proxy: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
return
}
fmt.Printf("Response from httpbin.org/ip via authenticated proxy:\n%s\n", body)
}
SOCKS5 proxy'leri kullanma
net/http doğrudan HTTP/HTTPS proxy'lerini desteklerken, SOCKS5 proxy'leri ek bir paket gerektirir: golang.org/x/net/proxy. SOCKS5, HTTP proxy'lerine göre daha alt bir katmanda çalışır ve çeşitli protokolleri destekler.
Bir SOCKS5 proxy'sini kullanmak için proxy paketiyle bir Dialer oluşturmanız ve bunu http.Transport yapısının DialContext alanına atamanız gerekir.
package main
import (
"context"
"fmt"
"io"
"net"
"net/http"
"time"
"golang.org/x/net/proxy" // Make sure to 'go get golang.org/x/net/proxy'
)
func main() {
socks5Proxy := "socks5://user:password@your_socks5_proxy_ip:port" // Replace with your SOCKS5 proxy
dialer, err := proxy.SOCKS5("tcp", socks5Proxy[len("socks5://"):], nil, proxy.Direct) // Basic auth is handled by the dialer
if err != nil {
fmt.Printf("Error creating SOCKS5 dialer: %v\n", err)
return
}
// For authenticated SOCKS5, the proxy.SOCKS5 function handles it if the URL contains credentials.
// If the proxy.SOCKS5 function doesn't parse credentials directly, you might need to
// extract them and pass a proxy.Auth struct. Example:
// auth := &proxy.Auth{User: "user", Password: "password"}
// dialer, err := proxy.SOCKS5("tcp", "your_socks5_proxy_ip:port", auth, proxy.Direct)
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
// Optionally, set TLSClientConfig for HTTPS over SOCKS5
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // Use with caution
},
Timeout: 10 * time.Second,
}
resp, err := client.Get("http://httpbin.org/ip")
if err != nil {
fmt.Printf("Error making request with SOCKS5 proxy: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
return
}
fmt.Printf("Response from httpbin.org/ip via SOCKS5 proxy:\n%s\n", body)
}
Dinamik proxy seçimi ve rotasyon
Web scraping veya büyük ölçekli veri toplama gibi uygulamalarda, IP engellemelerinden kaçınmak ve istek hacmini yönetmek için proxy rotasyonu şarttır. Bunun için http.Transport yapısına özel bir Proxy fonksiyonu gerekir.
http.Transport yapısının Proxy alanı, func(*http.Request) (*url.URL, error) imzasına sahip bir fonksiyon bekler. Bu fonksiyon her istekten önce çağrılır ve isteğin özelliklerine veya bir rotasyon stratejisine göre dinamik proxy seçimine olanak tanır.
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
)
// ProxyRotator manages a list of proxies and provides a round-robin selection.
type ProxyRotator struct {
proxies []*url.URL
current int
mu sync.Mutex
}
// NewProxyRotator creates a new ProxyRotator from a slice of proxy URLs.
func NewProxyRotator(proxyStrings []string) (*ProxyRotator, error) {
proxies := make([]*url.URL, len(proxyStrings))
for i, s := range proxyStrings {
u, err := url.Parse(s)
if err != nil {
return nil, fmt.Errorf("invalid proxy URL '%s': %w", s, err)
}
proxies[i] = u
}
return &ProxyRotator{
proxies: proxies,
current: 0,
}, nil
}
// GetProxy returns the next proxy in a round-robin fashion.
func (pr *ProxyRotator) GetProxy(_ *http.Request) (*url.URL, error) {
if len(pr.proxies) == 0 {
return nil, nil // No proxy
}
pr.mu.Lock()
defer pr.mu.Unlock()
proxy := pr.proxies[pr.current]
pr.current = (pr.current + 1) % len(pr.proxies)
return proxy, nil
}
func main() {
proxyList := []string{
"http://user1:[email protected]:8080",
"http://user2:[email protected]:8080",
"http://user3:[email protected]:8080",
}
rotator, err := NewProxyRotator(proxyList)
if err != nil {
fmt.Printf("Error creating proxy rotator: %v\n", err)
return
}
client := &http.Client{
Transport: &http.Transport{
Proxy: rotator.GetProxy, // Assign the rotator's GetProxy method
},
Timeout: 15 * time.Second,
}
for i := 0; i < 5; i++ {
resp, err := client.Get("http://httpbin.org/ip")
if err != nil {
fmt.Printf("Request %d error: %v\n", i+1, err)
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Request %d error reading body: %v\n", i+1, err)
continue
}
fmt.Printf("Request %d IP: %s\n", i+1, body)
time.Sleep(1 * time.Second) // Simulate some work
}
}
Bu örnek, temel bir round-robin rotasyon mekanizması uygular. Daha gelişmiş rotasyon mekanizmaları şunları yapabilir:
* Proxy sağlığını izlemek ve başarısız olan proxy'leri listeden çıkarmak.
* Farklı rotasyon stratejileri uygulamak (örneğin ağırlıklı, rastgele).
* Harici proxy yönetim servisleriyle entegre olmak.
Proxy üzerinden HTTPS trafiği
Bir http.Client bir HTTP proxy üzerinden https:// isteği yaptığında, http.Transport proxy ile CONNECT metodu üzerinden bir el sıkışma gerçekleştirir. İstemci, proxy'den hedef host ve porta (genellikle 443) bir TCP tüneli kurmasını ister. Tünel kurulduktan sonra istemci, TLS el sıkışmasını proxy tüneli üzerinden doğrudan hedef sunucuyla yapar. Proxy'nin kendisi HTTPS trafiğini şifresini çözmez (özel güven yapılandırmaları gerektiren farklı bir senaryo olan "man-in-the-middle" proxy olmadığı sürece).
https:// istekleri için Proxy fonksiyonu her zamanki gibi proxy URL'sini döndürmelidir. Go'nun net/http paketi CONNECT metodunu otomatik olarak yönetir.
TLS/SSL doğrulaması
Varsayılan olarak Go istemcileri katı TLS sertifika doğrulaması yapar. Hedef sunucunun sertifikası doğrulanamıyorsa (örneğin kendinden imzalı, süresi dolmuş veya güvenilmeyen bir CA), istek başarısız olur.
Belirli senaryolarda (örneğin kontrollü bir ortamda bilinen kendinden imzalı sertifikalarla test yaparken) tls.Config içindeki InsecureSkipVerify ile sertifika doğrulamasını devre dışı bırakabilirsiniz.
package main
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
func main() {
proxyStr := "http://your_proxy_ip:port"
proxyURL, err := url.Parse(proxyStr)
if err != nil {
fmt.Printf("Error parsing proxy URL: %v\n", err)
return
}
// Configure a custom Transport for TLS settings
tr := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{
// WARNING: InsecureSkipVerify should not be used in production environments.
// It disables certificate chain and hostname verification.
InsecureSkipVerify: true,
},
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{
Transport: tr,
Timeout: 15 * time.Second, // Overall request timeout
}
// Example target for HTTPS
resp, err := client.Get("https://example.com")
if err != nil {
fmt.Printf("Error making HTTPS request with proxy: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
return
}
fmt.Printf("Response status from https://example.com: %s\n", resp.Status)
// fmt.Printf("Response body: %s\n", body) // Omitted for brevity
_ = body
}
Karşılaştırma: HTTP/HTTPS ve SOCKS5 proxy'leri
| Özellik | HTTP/HTTPS proxy | SOCKS5 proxy |
|---|---|---|
| Protokol katmanı | Uygulama katmanı (Katman 7) | Oturum katmanı (Katman 5) |
| Desteklenen trafik | Öncelikle HTTP/HTTPS istekleri | Her türlü TCP/UDP trafiği (HTTP, FTP, SMTP, DNS vb.) |
| Trafik filtreleme | HTTP başlıklarını, URL'leri ve içeriği inceleyebilir | Genellikle trafik içeriğini incelemez |
| Kimlik doğrulama | HTTP Basic, Digest (Go yalnızca URL içinde Basic destekler) | Kullanıcı adı/parola (SOCKS5 AUTH yöntemi) |
| Kullanım kolaylığı (Go) | net/http içinde yerel destek (http.ProxyURL) |
golang.org/x/net/proxy paketi gerekir |
| Performans | HTTP ayrıştırması nedeniyle daha yüksek ek yük olabilir | Genellikle daha düşük ek yük, basit bir relay gibi çalışır |
| Kullanım alanları | Web scraping, API etkileşimi, web filtreleme | Genel ağ tünelleme, güvenlik duvarlarını aşma |
En iyi uygulamalar
- Timeout ayarlayın: Özellikle güvenilmez proxy'lerle çalışırken isteklerin süresiz askıda kalmaması için
http.Clientvehttp.Transportiçin her zaman timeout değerleri yapılandırın (örneğinDialContext,TLSHandshakeTimeout). - Sağlam hata yönetimi: Proxy kaynaklı hatalar değişkenlik gösterir (örneğin bağlantı sorunları için
net.OpError, beklenmeyen kopmalar içinio.EOF). Kapsamlı hata yönetimi ve yeniden deneme mantığı uygulayın. - Proxy yönetimi: Dinamik senaryolar için proxy sağlığını izleyen, proxy'leri etkin biçimde döndüren ve arızaları düzgün ele alan sağlam bir proxy yönetim sistemi kurun.
- Kaynak yönetimi: Ağ kaynaklarını serbest bırakmak için
resp.Body.Close()çağrısınındeferile yapıldığından emin olun. - İptal için Context: İstek iptali ve fonksiyonlar arası timeout yönetimi için
http.Requestile birliktecontext.Contextkullanın. InsecureSkipVerify'dan kaçının: Güvenlik sonuçlarını tam olarak anlamadığınız ve telafi edici denetimleriniz olmadığı sürece üretim ortamlarındaInsecureSkipVerify = truekullanmayın.
