00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

HTTP Client

Client HTTP in Go

Il pacchetto net/http di Go non include solo un server HTTP, ma anche un client completo per effettuare richieste HTTP. Il client predefinito e semplice da usare ma anche altamente configurabile per esigenze avanzate come timeout, header personalizzati e gestione del context.

http.Get

Il modo piu semplice per effettuare una richiesta GET:

package main

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

func main() {
    resp, err := http.Get("https://api.esempio.it/dati")
    if err != nil {
        fmt.Println("Errore nella richiesta:", err)
        return
    }
    defer resp.Body.Close() // IMPORTANTE: chiudere sempre il body

    // Leggere il body della risposta
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Errore nella lettura:", err)
        return
    }

    fmt.Println("Status:", resp.StatusCode)
    fmt.Println("Body:", string(body))
}

E fondamentale chiamare sempre defer resp.Body.Close() per evitare perdite di risorse (resource leak).

http.Post

Per effettuare richieste POST con un body:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type Utente struct {
    Nome  string `json:"nome"`
    Email string `json:"email"`
}

func main() {
    utente := Utente{
        Nome:  "Mario Rossi",
        Email: "mario@esempio.it",
    }

    // Serializzare i dati in JSON
    datiJSON, err := json.Marshal(utente)
    if err != nil {
        fmt.Println("Errore serializzazione:", err)
        return
    }

    // Inviare la richiesta POST
    resp, err := http.Post(
        "https://api.esempio.it/utenti",
        "application/json",
        bytes.NewBuffer(datiJSON),
    )
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }
    defer resp.Body.Close()

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

Client personalizzato

Il client predefinito (http.DefaultClient) non ha timeout, il che puo essere pericoloso in produzione. E buona pratica creare un client personalizzato:

package main

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

func main() {
    // Client con timeout
    client := &http.Client{
        Timeout: 10 * time.Second,
    }

    resp, err := client.Get("https://api.esempio.it/dati")
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }
    defer resp.Body.Close()

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

Per un controllo piu fine sui timeout:

import (
    "net"
    "net/http"
    "time"
)

func creaClient() *http.Client {
    transport := &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,  // Timeout di connessione
            KeepAlive: 30 * time.Second, // Keep-alive
        }).DialContext,
        TLSHandshakeTimeout:   5 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        MaxIdleConns:          100,
        MaxIdleConnsPerHost:   10,
        IdleConnTimeout:       90 * time.Second,
    }

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

Impostare Header

Per personalizzare gli header della richiesta, si crea una *http.Request manualmente:

package main

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

func main() {
    client := &http.Client{Timeout: 10 * time.Second}

    // Creare la richiesta manualmente
    req, err := http.NewRequest("GET", "https://api.esempio.it/dati", nil)
    if err != nil {
        fmt.Println("Errore nella creazione richiesta:", err)
        return
    }

    // Aggiungere header
    req.Header.Set("Authorization", "Bearer token-abc123")
    req.Header.Set("Accept", "application/json")
    req.Header.Set("User-Agent", "MioClient/1.0")
    req.Header.Set("X-Custom-Header", "valore")

    // Eseguire la richiesta
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }
    defer resp.Body.Close()

    // Leggere gli header della risposta
    contentType := resp.Header.Get("Content-Type")
    fmt.Println("Content-Type:", contentType)

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

Gestione degli errori

Una gestione completa degli errori include il controllo dello status code:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type RispostaErrore struct {
    Codice    int    `json:"codice"`
    Messaggio string `json:"messaggio"`
}

func chiamaAPI(url string) ([]byte, error) {
    client := &http.Client{Timeout: 10 * time.Second}

    resp, err := client.Get(url)
    if err != nil {
        return nil, fmt.Errorf("errore di rete: %w", err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("errore lettura body: %w", err)
    }

    // Verificare lo status code
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        var errResp RispostaErrore
        if err := json.Unmarshal(body, &errResp); err == nil {
            return nil, fmt.Errorf("errore API [%d]: %s",
                errResp.Codice, errResp.Messaggio)
        }
        return nil, fmt.Errorf("errore HTTP %d: %s",
            resp.StatusCode, http.StatusText(resp.StatusCode))
    }

    return body, nil
}

func main() {
    dati, err := chiamaAPI("https://api.esempio.it/utenti")
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }
    fmt.Println("Dati ricevuti:", string(dati))
}

Context con le richieste

Il context permette di controllare il timeout e la cancellazione delle richieste:

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "time"
)

func ottieniDati(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("errore creazione richiesta: %w", err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("errore richiesta: %w", err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

func main() {
    // Context con timeout di 5 secondi
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    dati, err := ottieniDati(ctx, "https://api.esempio.it/dati")
    if err != nil {
        fmt.Println("Errore:", err)
        return
    }

    fmt.Println("Ricevuti:", len(dati), "byte")
}

Richieste con diversi metodi HTTP

Un helper per effettuare richieste con qualsiasi metodo:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type ClientAPI struct {
    BaseURL string
    Client  *http.Client
    Token   string
}

func NuovoClientAPI(baseURL, token string) *ClientAPI {
    return &ClientAPI{
        BaseURL: baseURL,
        Client:  &http.Client{Timeout: 15 * time.Second},
        Token:   token,
    }
}

func (c *ClientAPI) Richiesta(metodo, percorso string, body any) ([]byte, error) {
    var bodyReader *bytes.Buffer
    if body != nil {
        dati, err := json.Marshal(body)
        if err != nil {
            return nil, err
        }
        bodyReader = bytes.NewBuffer(dati)
    } else {
        bodyReader = &bytes.Buffer{}
    }

    req, err := http.NewRequest(metodo, c.BaseURL+percorso, bodyReader)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer "+c.Token)

    resp, err := c.Client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

func main() {
    api := NuovoClientAPI("https://api.esempio.it", "mio-token")

    // GET
    dati, _ := api.Richiesta("GET", "/utenti", nil)
    fmt.Println(string(dati))

    // POST
    nuovoUtente := map[string]string{"nome": "Luigi", "email": "luigi@esempio.it"}
    dati, _ = api.Richiesta("POST", "/utenti", nuovoUtente)
    fmt.Println(string(dati))
}

Conclusione

Il client HTTP di Go e potente e flessibile. Per applicazioni di produzione, create sempre un client personalizzato con timeout appropriati, usate http.NewRequestWithContext per supportare la cancellazione tramite context, e gestite sempre gli errori controllando sia l’errore di rete che lo status code HTTP. Ricordate di chiudere sempre il body della risposta con defer resp.Body.Close() per evitare perdite di risorse.