Torna al blog

Configurazione del proxy in Go (Golang): http.Client, SOCKS5 e rotazione IP per scraping e automazione

Come configurare correttamente un server proxy in Go tramite http.Client: dalla configurazione di base alla rotazione degli IP e all'uso di SOCKS5. Guida completa con esempi di codice.

📅11 agosto 2026

Se stai scrivendo un parser, automatizzando la raccolta di dati o testando API da diverse regioni, prima o poi ti imbatterai in blocchi per IP. Go è uno dei linguaggi più popolari per scrivere strumenti di rete ad alte prestazioni, e la configurazione del proxy qui ha le sue peculiarità. In questa guida esamineremo tutto, dalla configurazione di base di http.Client fino alla rotazione avanzata degli IP con supporto SOCKS5.

Perché sono necessari i proxy nelle applicazioni Go

Go è diventato lo standard de facto per la scrittura di strumenti di rete ad alta intensità: parser, crawler, bot per il monitoraggio dei prezzi, strumenti di automazione API. È qui che la necessità di un proxy si presenta più frequentemente — ed è qui che è importante configurare tutto correttamente fin dall'inizio.

Gli scenari principali in cui non puoi fare a meno di un proxy:

  • Parsing di marketplace — Wildberries, Ozon, AliExpress bloccano gli IP dopo 50–200 richieste da un singolo indirizzo. Senza cambio IP, il parser si ferma.
  • Geo-testing — verifica dei risultati dei motori di ricerca, degli annunci pubblicitari o dei prezzi da diversi paesi e regioni.
  • Bypass del rate-limit — distribuire il carico su più IP consente di superare le limitazioni sul numero di richieste al minuto.
  • Anonimato — nascondere il vero IP del server quando si lavora con API esterne.
  • Test di CDN e geolocalizzazione — verifica della correttezza della consegna dei contenuti per diverse regioni.

La libreria standard di Go fornisce strumenti flessibili per lavorare con i proxy — tramite http.Transport e http.Client. Esamineremo ogni metodo in dettaglio.

Configurazione di base del proxy HTTP tramite http.Client

In Go, il proxy viene configurato a livello di http.Transport — il livello di trasporto del client HTTP. Questo offre un controllo completo su quale proxy vengono utilizzate le richieste e consente di creare più client con proxy diversi contemporaneamente.

Un esempio minimo funzionante di configurazione del proxy HTTP:

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    // Specifica l'indirizzo del server proxy
    proxyURL, err := url.Parse("http://123.45.67.89:8080")
    if err != nil {
        panic(err)
    }

    // Crea un trasporto con il proxy
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }

    // Crea un client con il nostro trasporto
    client := &http.Client{
        Transport: transport,
    }

    // Fai una richiesta tramite il proxy
    resp, err := client.Get("https://httpbin.org/ip")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Cosa succede in questo codice:

  1. url.Parse — analizza la stringa con l'indirizzo del proxy in una struttura *url.URL
  2. http.Transport{Proxy: http.ProxyURL(proxyURL)} — crea un trasporto che instrada tutte le richieste attraverso il proxy specificato
  3. http.Client{Transport: transport} — crea un client con il nostro trasporto

Un punto importante: se utilizzi http.DefaultClient (cioè semplicemente http.Get() senza un client esplicito), il proxy non verrà applicato. Crea sempre il tuo http.Client per lavorare con il proxy.

💡 Suggerimento

Aggiungi un timeout a http.Client: Timeout: 30 * time.Second. I server proxy possono rispondere più lentamente rispetto a una connessione diretta, e senza timeout le goroutine rimarranno bloccate indefinitamente.

Proxy tramite variabili d'ambiente

Go supporta le variabili d'ambiente standard per i proxy — HTTP_PROXY, HTTPS_PROXY e NO_PROXY. Il trasporto standard http.DefaultTransport li legge automaticamente tramite la funzione http.ProxyFromEnvironment.

Imposta le variabili d'ambiente prima di eseguire l'applicazione:

# Nel terminale (Linux/macOS)
export HTTP_PROXY="http://123.45.67.89:8080"
export HTTPS_PROXY="http://123.45.67.89:8080"
export NO_PROXY="localhost,127.0.0.1"

# Avvia l'applicazione
go run main.go

Se desideri utilizzare ProxyFromEnvironment nel tuo trasporto (ad esempio, con impostazioni TLS aggiuntive), procedi in questo modo:

transport := &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    // Impostazioni aggiuntive...
    TLSHandshakeTimeout:   10 * time.Second,
    ResponseHeaderTimeout: 30 * time.Second,
}

client := &http.Client{
    Transport: transport,
    Timeout:   60 * time.Second,
}

Questo approccio è comodo per ambienti di produzione, dove l'indirizzo del proxy è specificato tramite la configurazione di distribuzione (Docker, Kubernetes, systemd), piuttosto che essere hardcoded nel codice. Inoltre, semplifica il cambio di proxy senza ricompilare l'applicazione.

Connessione a un proxy SOCKS5 in Golang

SOCKS5 è un protocollo più versatile rispetto ai proxy HTTP. Funziona a un livello più basso e supporta qualsiasi connessione TCP/UDP, non solo HTTP/HTTPS. Per lavorare con parser e automazione, SOCKS5 è spesso preferibile — soprattutto quando si utilizzano proxy residenziali, che vengono forniti più frequentemente proprio tramite questo protocollo.

Per lavorare con SOCKS5 in Go è necessario il pacchetto golang.org/x/net/proxy:

go get golang.org/x/net/proxy

Ecco un esempio di connessione a un proxy SOCKS5 senza autenticazione:

package main

import (
    "fmt"
    "io"
    "net/http"
    "golang.org/x/net/proxy"
)

func main() {
    // Crea un dialer SOCKS5
    dialer, err := proxy.SOCKS5("tcp", "123.45.67.89:1080", nil, proxy.Direct)
    if err != nil {
        panic(err)
    }

    // Crea un trasporto con il dialer personalizzato
    transport := &http.Transport{
        Dial: dialer.Dial,
    }

    client := &http.Client{
        Transport: transport,
    }

    resp, err := client.Get("https://httpbin.org/ip")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

A partire da Go 1.20+, è possibile utilizzare anche il supporto integrato per SOCKS5 tramite url.Parse con il prefisso socks5://:

proxyURL, _ := url.Parse("socks5://123.45.67.89:1080")

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{Transport: transport}

Questa sintassi è più semplice e non richiede dipendenze aggiuntive. Tuttavia, per scenari più complessi (ad esempio, SOCKS5 con autenticazione in versioni precedenti di Go), il pacchetto golang.org/x/net/proxy rimane rilevante.

Autenticazione: nome utente e password nel proxy

La maggior parte dei fornitori di proxy commerciali utilizza l'autenticazione tramite nome utente e password. Ci sono due modi per trasmettere le credenziali in Go.

Metodo 1: Incorporare nell'URL

// Formato: protocollo://nomeutente:password@host:porta
proxyURL, err := url.Parse("http://myuser:[email protected]:8080")
if err != nil {
    panic(err)
}

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

Metodo 2: Tramite la struttura url.URL (più sicuro)

proxyURL := &url.URL{
    Scheme: "http",
    Host:   "proxy.example.com:8080",
    User:   url.UserPassword("myuser", "mypassword"),
}

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

Il secondo metodo è preferibile — gestisce correttamente i caratteri speciali nella password (ad esempio, @, #, :), che potrebbero interrompere l'analisi dell'URL nel primo caso.

Per SOCKS5 con autenticazione tramite il pacchetto golang.org/x/net/proxy:

auth := &proxy.Auth{
    User:     "myuser",
    Password: "mypassword",
}

dialer, err := proxy.SOCKS5("tcp", "proxy.example.com:1080", auth, proxy.Direct)
if err != nil {
    panic(err)
}

transport := &http.Transport{
    Dial: dialer.Dial,
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

⚠️ Sicurezza

Non hardcodare mai il nome utente e la password del proxy direttamente nel codice. Utilizza variabili d'ambiente o file di configurazione che non vengono inclusi nel repository. Aggiungi .env a .gitignore.

Rotazione del proxy: cambio IP tra le richieste

Un proxy è utile per il test, ma per un vero parsing è necessaria la rotazione degli IP. I siti monitorano la frequenza delle richieste da un singolo indirizzo, e anche un buon proxy verrà bloccato se si inviano migliaia di richieste consecutive.

Ci sono due approcci alla rotazione in Go:

Approccio 1: Lista di proxy con round-robin

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "sync/atomic"
    "time"
)

type ProxyRotator struct {
    proxies []*url.URL
    counter uint64
}

func NewProxyRotator(proxyURLs []string) (*ProxyRotator, error) {
    proxies := make([]*url.URL, 0, len(proxyURLs))
    for _, p := range proxyURLs {
        u, err := url.Parse(p)
        if err != nil {
            return nil, err
        }
        proxies = append(proxies, u)
    }
    return &ProxyRotator{proxies: proxies}, nil
}

// GetClient restituisce un client con il prossimo proxy in rotazione
func (r *ProxyRotator) GetClient() *http.Client {
    idx := atomic.AddUint64(&r.counter, 1) % uint64(len(r.proxies))
    proxyURL := r.proxies[idx]

    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }

    return &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }
}

func main() {
    proxyList := []string{
        "http://user:[email protected]:8080",
        "http://user:[email protected]:8080",
        "http://user:[email protected]:8080",
    }

    rotator, err := NewProxyRotator(proxyList)
    if err != nil {
        panic(err)
    }

    // Ogni richiesta passa attraverso il prossimo proxy
    for i := 0; i < 10; i++ {
        client := rotator.GetClient()
        resp, err := client.Get("https://httpbin.org/ip")
        if err != nil {
            fmt.Printf("Errore nella richiesta %d: %v\n", i, err)
            continue
        }
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()
        fmt.Printf("Richiesta %d: %s\n", i, string(body))
    }
}

Approccio 2: Selezione dinamica del proxy tramite funzione

Il campo Proxy in http.Transport accetta una funzione con la firma func(*http.Request) (*url.URL, error). Questo consente di selezionare il proxy dinamicamente per ogni richiesta:

proxies := []*url.URL{
    mustParseURL("http://proxy1.example.com:8080"),
    mustParseURL("http://proxy2.example.com:8080"),
    mustParseURL("http://proxy3.example.com:8080"),
}

var counter uint64

transport := &http.Transport{
    Proxy: func(req *http.Request) (*url.URL, error) {
        // Seleziona il proxy in base alla richiesta (round-robin)
        idx := atomic.AddUint64(&counter, 1) % uint64(len(proxies))
        return proxies[idx], nil
    },
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

func mustParseURL(s string) *url.URL {
    u, err := url.Parse(s)
    if err != nil {
        panic(err)
    }
    return u
}

Il secondo approccio è più elegante: un client ruota automaticamente i proxy per ogni richiesta. Questo è particolarmente comodo per richieste concorrenti tramite goroutine.

Quale tipo di proxy scegliere per un progetto Go

La scelta del tipo di proxy influisce direttamente sul successo del tuo progetto. Per compiti diversi sono adatti diversi tipi — esaminiamo le opzioni principali:

Tipo di proxy Velocità Anonimato Meglio per
Data center ⚡ Alta Media Parsing di dati aperti, test di API, test di carico
Residenziali 🔄 Media Alta Parsing di marketplace (Wildberries, Ozon), bypass della protezione anti-bot
Mobile 🔄 Media Massima Lavoro con i social media, servizi con severa protezione anti-bot

Per il parsing di marketplace (Wildberries, Ozon, Avito): utilizza proxy residenziali. Queste piattaforme utilizzano attivamente sistemi di rilevamento bot (Cloudflare, soluzioni proprietarie) e bloccano facilmente gli IP dei data center. Gli IP residenziali appaiono come normali utenti domestici.

Per il testing delle API e il testing di carico: i proxy dei data center sono la scelta ottimale. Alta velocità, connessione stabile, latenza prevedibile.

Per il geo-testing: proxy residenziali con selezione di paese/città. Consentono di verificare come il tuo servizio o pubblicità appare per gli utenti di una specifica regione.

Errori comuni e migliori pratiche

Negli anni di lavoro con i proxy nei progetti Go, si è cristallizzata una lista di errori tipici che rallentano lo sviluppo e riducono l'affidabilità dei parser. Esaminiamo ciascuno di essi.

Errore 1: Riutilizzo del trasporto senza reset delle connessioni

http.Transport memorizza le connessioni TCP (keep-alive). Se crei un nuovo trasporto per ogni richiesta, perdi questo vantaggio. Ma se riutilizzi un trasporto con un proxy fisso durante la rotazione, le richieste possono passare attraverso una vecchia connessione, ignorando il nuovo proxy.

Soluzione: quando cambi proxy, chiama transport.CloseIdleConnections() o utilizza un approccio funzionale con selezione dinamica del proxy (descritto nella sezione sulla rotazione).

Errore 2: Nessuna gestione degli errori del proxy

I proxy possono essere non disponibili, sovraccarichi o bloccati dal sito di destinazione. Senza logica di retry, il tuo parser si fermerà al primo errore.

func fetchWithRetry(client *http.Client, targetURL string, maxRetries int) ([]byte, error) {
    var lastErr error
    for i := 0; i < maxRetries; i++ {
        resp, err := client.Get(targetURL)
        if err != nil {
            lastErr = err
            time.Sleep(time.Duration(i+1) * time.Second) // Ritardo esponenziale
            continue
        }
        defer resp.Body.Close()

        if resp.StatusCode == 429 || resp.StatusCode == 403 {
            lastErr = fmt.Errorf("bloccato: stato %d", resp.StatusCode)
            time.Sleep(time.Duration(i+1) * 2 * time.Second)
            continue
        }

        return io.ReadAll(resp.Body)
    }
    return nil, fmt.Errorf("tutti i tentativi esauriti: %w", lastErr)
}

Errore 3: Ignorare la verifica TLS

Alcuni sviluppatori disabilitano la verifica dei certificati TLS per i proxy (InsecureSkipVerify: true). Questo crea una vulnerabilità agli attacchi MITM. Se il server proxy utilizza un certificato autofirmato, aggiungilo al pool di fiducia, invece di disabilitare completamente la verifica.

Errore 4: Intestazioni identiche per tutte le richieste

Anche con la rotazione dei proxy, potresti essere bloccato a causa del pattern delle intestazioni. Cambia User-Agent insieme al proxy, aggiungi intestazioni realistiche del browser:

userAgents := []string{
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/119.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/118.0.0.0 Safari/537.36",
}

req, _ := http.NewRequest("GET", targetURL, nil)
req.Header.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
req.Header.Set("Accept-Encoding", "gzip, deflate, br")

Checklist per un client proxy affidabile in Go

  • ✅ Timeout sul client (Timeout: 30 * time.Second)
  • ✅ Logica di retry con ritardo esponenziale
  • ✅ Rotazione del proxy in caso di errori 403/429
  • ✅ Rotazione dell'User-Agent insieme al proxy
  • ✅ Registrazione degli errori con indicazione del proxy utilizzato
  • ✅ Credenziali tramite variabili d'ambiente, non hardcoded
  • ✅ Chiusura di resp.Body tramite defer
  • ✅ Limitazione della concorrenza tramite semaforo o worker pool

Esempio di un worker pool completo con rotazione del proxy

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "sync"
    "sync/atomic"
    "time"
)

type Scraper struct {
    proxies []*url.URL
    counter uint64
    workers int
}

func (s *Scraper) getClient() *http.Client {
    idx := atomic.AddUint64(&s.counter, 1) % uint64(len(s.proxies))
    return &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(s.proxies[idx]),
        },
        Timeout: 30 * time.Second,
    }
}

func (s *Scraper) Scrape(urls []string) map[string]string {
    results := make(map[string]string)
    var mu sync.Mutex
    var wg sync.WaitGroup

    sem := make(chan struct{}, s.workers) // Semaforo per limitare la concorrenza

    for _, u := range urls {
        wg.Add(1)
        go func(targetURL string) {
            defer wg.Done()
            sem <- struct{}{}
            defer func() { <-sem }()

            client := s.getClient()
            resp, err := client.Get(targetURL)
            if err != nil {
                fmt.Printf("Errore: %v\n", err)
                return
            }
            defer resp.Body.Close()

            body, _ := io.ReadAll(resp.Body)

            mu.Lock()
            results[targetURL] = string(body)
            mu.Unlock()
        }(u)
    }

    wg.Wait()
    return results
}

Conclusione

Configurare un proxy in Go è un compito che può essere risolto con poche righe di codice, ma richiede una comprensione delle sfumature: uso corretto di http.Transport, gestione corretta dell'autenticazione, rotazione degli IP e gestione degli errori. La libreria standard fornisce tutto il necessario per i proxy HTTP, e il pacchetto golang.org/x/net/proxy soddisfa la necessità di SOCKS5.

Le conclusioni chiave dell'articolo:

  • Creare sempre il proprio http.Clienthttp.DefaultClient non supporta i proxy direttamente
  • Utilizzare un approccio funzionale (Proxy: func(r *Request) (*url.URL, error)) per la rotazione dinamica
  • Conservare le credenziali in variabili d'ambiente, non nel codice
  • Aggiungere logica di retry — i proxy non sono mai al 100% affidabili
  • Cambiare l'User-Agent insieme al proxy per la massima efficacia

Se stai scrivendo un parser per marketplace come Wildberries o Ozon, ti consigliamo di utilizzare proxy residenziali — hanno IP reali di utenti domestici e vengono bloccati molto meno frequentemente dai sistemi anti-bot. Per scenari ad alta intensità con migliaia di richieste al minuto, la scelta ottimale saranno i proxy dei data center — forniscono la massima velocità e stabilità della connessione.