net/http.Client în Go

Clientul HTTP configurabil (limită de timp, redirecționări, cookie-uri); Do trimite o cerere, Get o cerere GET.

Sintaxă
type Client struct {
    Transport     RoundTripper
    CheckRedirect func(req *Request, via []*Request) error
    Jar           CookieJar
    Timeout       time.Duration
}
func (c *Client) Do(req *Request) (*Response, error)
func (c *Client) Get(url string) (resp *Response, err error)

Ce face net/http.Client

http.Client trimite cereri și primește răspunsuri. http.Get și celelalte funcții folosesc http.DefaultClient, care nu are limită de timp. În programe reale creezi propriul client, o singură dată, și îl refolosești: client := &http.Client{Timeout: 10 * time.Second}. Clientul este sigur pentru folosire din mai multe goroutine și refolosește conexiunile.

client.Do(req) trimite o cerere construită cu http.NewRequest, iar client.Get(url) este scurtătura pentru GET. Timeout limitează toată cererea, inclusiv citirea corpului. CheckRedirect controlează redirecționările (returnează http.ErrUseLastResponse ca să nu le urmezi), iar Jar păstrează cookie-urile între cereri (net/http/cookiejar).

Ce returnează

Do și Get: răspunsul și o eroare. O eroare apare doar la probleme de rețea, timp depășit sau redirecționări refuzate, nu la coduri 4xx/5xx.

Exemple

Exemplul 1: Client cu limită de timp

package main

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

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/lent" {
			select {
			case <-time.After(time.Second):
			case <-r.Context().Done():
				return
			}
		}
		fmt.Fprint(w, "răspuns pentru ", r.URL.Path)
	}))
	defer srv.Close()

	client := &http.Client{Timeout: 100 * time.Millisecond}
	for _, cale := range []string{"/rapid", "/lent"} {
		resp, err := client.Get(srv.URL + cale)
		if ne, ok := errors.AsType[net.Error](err); ok && ne.Timeout() {
			fmt.Println(cale, "-> timpul a expirat")
			continue
		} else if err != nil {
			fmt.Println(cale, "-> eroare:", err)
			continue
		}
		corp, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		fmt.Println(cale, "->", string(corp))
	}
}

Rezultat

/rapid -> răspuns pentru /rapid
/lent -> timpul a expirat

Exemplul 2: Do cu cerere proprie și redirecționări oprite

CheckRedirect returnează http.ErrUseLastResponse, deci clientul nu urmează redirecționarea și primești chiar răspunsul 302.

package main

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

func main() {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/vechi" {
			http.Redirect(w, r, "/nou", http.StatusFound)
			return
		}
		fmt.Fprint(w, "pagina nouă")
	}))
	defer srv.Close()

	normal := &http.Client{}
	fara := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	for _, x := range []struct {
		nume string
		c    *http.Client
	}{{"normal", normal}, {"fără redirect", fara}} {
		req, _ := http.NewRequest("GET", srv.URL+"/vechi", nil)
		resp, err := x.c.Do(req)
		if err != nil {
			fmt.Println(err)
			return
		}
		resp.Body.Close()
		fmt.Printf("%-14s %d, Location=%q, adresa finală=%s\n", x.nume, resp.StatusCode, resp.Header.Get("Location"), resp.Request.URL.Path)
	}
}

Rezultat

normal         200, Location="", adresa finală=/nou
fără redirect  302, Location="/nou", adresa finală=/vechi

Greșeli frecvente

  • Creezi un http.Client nou la fiecare cerere: conexiunile nu mai sunt refolosite. Creează-l o dată (la nivel de pachet sau în structura serviciului).
  • Folosești http.DefaultClient sau http.Get pentru servicii externe: fără Timeout, o cerere blocată ține goroutine-ul pentru totdeauna.
  • Modifici http.DefaultClient (de exemplu Timeout): schimbarea afectează și bibliotecile care îl folosesc.

Comenzi înrudite

  • net/http.NewRequest: Construiește o cerere HTTP completă (orice metodă, antete, corp) care se trimite apoi cu Client.Do.
  • net/http.Get: Trimite o cerere GET (sau HEAD, doar antete) la adresa dată și returnează răspunsul; corpul trebuie închis.
  • net/http.Response: Răspunsul primit de client: codul de stare (StatusCode, Status), antetele (Header) și corpul (Body), care trebuie închis.
  • net/http.Redirect: Trimite clientul la altă adresă (antetul Location) cu un cod 3xx: 301 mutat definitiv, 302/303 temporar.