Una richiesta GET
Ogni esempio di questa pagina avvia il proprio server con httptest.NewServer e lo chiama attraverso una vera connessione TCP su localhost. Sostituisci srv.URL con un indirizzo reale come https://api.example.com e il codice del client resta identico.
Le tre regole che si vedono qui:
- Controlla prima
err. Segnala i casi in cui non arriva proprio nessuna risposta: errori DNS, connessioni rifiutate, timeout, problemi TLS. defer resp.Body.Close()subito dopo il controllo dell'errore. Il body tiene aperta la connessione. Un body non chiuso fa perdere la connessione.- Leggi o decodifica il body.
io.ReadAllper il testo,json.NewDecoder(resp.Body).Decode(&v)per il JSON.
Gli status code non sono errori
http.Get restituisce err == nil per qualsiasi risposta mandi il server, compresi 404 e 500. Lo status devi controllarlo tu:
io.LimitReader limita quanta parte di un body di errore leggi, così un server che si comporta male non può farti caricare megabyte in un messaggio di errore. Molte API restituiscono 201, 202 o 204 in caso di successo; controlla resp.StatusCode >= 200 && resp.StatusCode < 300 quando va bene qualsiasi 2xx.
Timeout: mai il client di default per il traffico reale
http.Get, http.Post e http.DefaultClient non hanno un timeout complessivo (il transport di default limita solo la connessione e l'handshake TLS). Un server che accetta la connessione e non risponde mai blocca la tua goroutine per sempre. Crea il tuo client:
Client.Timeout copre l'intero scambio: connessione, redirect e lettura del body. L'errore che restituisce è un *url.Error che riporta Timeout() == true, e errors.As lo trova attraverso l'interfaccia net.Error. Crea un solo client e riusalo ovunque; un http.Client è sicuro per l'uso concorrente. Il pool di connessioni vive nel suo Transport, non nel Client: un nuovo http.Client{} per ogni richiesta condivide comunque http.DefaultTransport e il suo pool, mentre un nuovo http.Transport per ogni richiesta parte ogni volta con un pool vuoto.
Richieste con context e header
http.NewRequestWithContext costruisce una richiesta che puoi personalizzare prima di inviarla con client.Do. Il context aggiunge una scadenza o una cancellazione per singola richiesta sopra il timeout del client, e lega le chiamate in uscita alla durata di una richiesta in arrivo in un server.
Concatenare l'input dell'utente in un URL ("?q=" + q) si rompe con &, # e gli spazi. url.Values e il suo metodo Encode fanno l'escaping e ordinano le chiavi. Per i segmenti di percorso, usa url.PathEscape.
POST di JSON
client.Post(url, contentType, body) è una scorciatoia per una richiesta con quell'header Content-Type. Per tutto il resto (un header di autenticazione, PUT, PATCH, DELETE) usa http.NewRequestWithContext con il metodo e client.Do. Per l'invio di form, client.PostForm(url, url.Values{...}) codifica i valori e imposta application/x-www-form-urlencoded.
Riuso delle connessioni
Il client riusa una connessione TCP solo quando leggi fino in fondo il body precedente e lo chiudi. Se smetti di leggere prima (per esempio dopo uno status di errore), svuotalo prima di chiuderlo quando fai molte richieste allo stesso host:
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
Per body che potrebbero essere enormi, svuotali invece attraverso io.LimitReader, oppure accetta di perdere la connessione.
Redirect
Il client segue automaticamente fino a 10 redirect, e resp.Request.URL ti dice dove sei arrivato. Per fermarti al primo redirect, per esempio per leggere un header Location, imposta CheckRedirect:
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // return the 3xx response itself
},
}
Errori comuni
- Usare
http.Getohttp.DefaultClientin produzione. Nessun timeout. Crea unhttp.ClientconTimeout. - Dimenticare
resp.Body.Close(). Le connessioni si perdono finché le richieste iniziano a fallire. - Chiudere il body prima di controllare
err. Quandoerrnon ènil,respènileresp.Body.Close()va in panic. - Considerare
err == nilun successo. Controllaresp.StatusCode. - Creare un nuovo
http.Transportper ogni richiesta. Ogni transport ha il suo pool di connessioni, quindi ogni richiesta apre una connessione nuova (e un nuovo handshake TLS). Condividi un solo transport, di solito attraverso un unicohttp.Clientcondiviso. - Costruire le query string a mano. Usa
url.Values.
Domande frequenti
Come faccio una richiesta GET in Go?
resp, err := http.Get(url), controlla err, poi defer resp.Body.Close() e leggi il body con io.ReadAll(resp.Body) oppure decodificalo con json.NewDecoder(resp.Body).Decode(&v). Controlla anche resp.StatusCode: un 404 o un 500 non è un errore per http.Get.
Come imposto un timeout su una richiesta HTTP in Go?
Crea un client con un timeout, client := &http.Client{Timeout: 10 * time.Second}, e usalo al posto di http.Get o http.DefaultClient, che non hanno un timeout complessivo: un server che accetta la connessione e non risponde mai blocca la chiamata per sempre. Per un limite per singola richiesta, costruisci la richiesta con http.NewRequestWithContext e un context da context.WithTimeout.
Come invio una richiesta POST con un body JSON in Go?
Fai il marshal del valore, poi invialo: body, _ := json.Marshal(v) e http.Post(url, "application/json", bytes.NewReader(body)). Per aggiungere header come Authorization, costruiscila con http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)), imposta req.Header.Set("Content-Type", "application/json") e inviala con client.Do(req).
Perché devo chiudere resp.Body in Go?
Il body è uno stream su una connessione aperta. Chiuderlo restituisce la connessione al pool per riusarla; dimenticarsene fa perdere connessioni e file descriptor finché il programma non raggiunge il suo limite. Chiudilo anche quando non lo leggi, e leggilo fino in fondo se vuoi che la connessione venga riusata.