Menu

Client HTTP en Golang : GET, POST JSON, timeouts et en-têtes

Comment faire des requêtes HTTP en Go avec net/http : http.Get, lire et fermer le body, vérifier les codes de statut, les timeouts de http.Client, les requêtes avec context et en-têtes, les paramètres de requête, et envoyer du JSON en POST.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Une requête GET

Chaque exemple de cette page démarre son propre serveur avec httptest.NewServer et l'appelle via une vraie connexion TCP sur localhost. Remplacez srv.URL par une vraie adresse comme https://api.example.com et le code client ne change pas.

Les trois règles visibles ici :

  1. Vérifiez err en premier. Elle signale l'impossibilité d'obtenir la moindre réponse : erreurs DNS, connexions refusées, timeouts, problèmes TLS.
  2. defer resp.Body.Close() juste après la vérification d'erreur. Le body garde la connexion ouverte. Un body non fermé fait fuir la connexion.
  3. Lisez ou décodez le body. io.ReadAll pour du texte, json.NewDecoder(resp.Body).Decode(&v) pour du JSON.

Les codes de statut ne sont pas des erreurs

http.Get renvoie err == nil pour toute réponse envoyée par le serveur, y compris 404 et 500. Vous devez vérifier le statut vous-même :

io.LimitReader plafonne la quantité lue d'un body d'erreur, pour qu'un serveur défaillant ne vous fasse pas charger des mégaoctets dans un message d'erreur. Beaucoup d'API renvoient 201, 202 ou 204 en cas de succès ; vérifiez resp.StatusCode >= 200 && resp.StatusCode < 300 quand n'importe quel 2xx convient.

Timeouts : n'utilisez jamais le client par défaut pour du vrai trafic

http.Get, http.Post et http.DefaultClient n'ont aucun timeout global (le transport par défaut ne limite que l'établissement de la connexion et la négociation TLS). Un serveur qui accepte la connexion et ne répond jamais bloque votre goroutine indéfiniment. Créez votre propre client :

Client.Timeout couvre tout l'échange : la connexion, les redirections et la lecture du body. L'erreur qu'il renvoie est une *url.Error qui indique Timeout() == true, que errors.As trouve via l'interface net.Error. Créez un seul client et réutilisez-le partout ; un http.Client peut être utilisé en concurrence sans risque. Le pool de connexions vit dans son Transport, pas dans le Client : un nouveau http.Client{} par requête partage toujours http.DefaultTransport et son pool, mais un nouveau http.Transport par requête repart chaque fois d'un pool vide.

Requêtes avec un context et des en-têtes

http.NewRequestWithContext construit une requête que vous pouvez personnaliser avant de l'envoyer avec client.Do. Le context ajoute une échéance ou une annulation par requête en plus du timeout du client, et lie les appels sortants à la durée de vie d'une requête entrante dans un serveur.

Concaténer une saisie utilisateur dans une URL ("?q=" + q) casse avec &, # et les espaces. url.Values et sa méthode Encode font l'échappement, et trient les clés. Pour les segments de chemin, utilisez url.PathEscape.

POST JSON

client.Post(url, contentType, body) est un raccourci pour une requête avec cet en-tête Content-Type. Pour tout le reste (un en-tête d'authentification, PUT, PATCH, DELETE), utilisez http.NewRequestWithContext avec la méthode et client.Do. Pour les envois de formulaire, client.PostForm(url, url.Values{...}) encode les valeurs et définit application/x-www-form-urlencoded.

Réutilisation des connexions

Le client ne réutilise une connexion TCP que si vous lisez le body précédent jusqu'au bout et le fermez. Si vous arrêtez de lire plus tôt (par exemple après un statut d'erreur), videz-le avant de le fermer quand vous faites de nombreuses requêtes vers le même hôte :

defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()

Pour des bodies qui pourraient être énormes, videz plutôt via io.LimitReader, ou acceptez de perdre la connexion.

Redirections

Le client suit automatiquement jusqu'à 10 redirections, et resp.Request.URL vous indique où vous avez abouti. Pour vous arrêter à la première redirection, par exemple pour lire un en-tête Location, définissez 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
	},
}

Erreurs courantes

  • Utiliser http.Get ou http.DefaultClient en production. Pas de timeout. Créez un http.Client avec Timeout.
  • Oublier resp.Body.Close(). Les connexions fuient jusqu'à ce que les requêtes commencent à échouer.
  • Fermer le body avant de vérifier err. Quand err n'est pas nil, resp vaut nil et resp.Body.Close() provoque un panic.
  • Considérer err == nil comme un succès. Vérifiez resp.StatusCode.
  • Créer un nouveau http.Transport par requête. Chaque transport a son propre pool de connexions, donc chaque requête ouvre une nouvelle connexion (et une nouvelle négociation TLS). Partagez un seul transport, généralement via un seul http.Client partagé.
  • Construire les chaînes de requête à la main. Utilisez url.Values.

Questions fréquentes

Comment faire une requête GET en Go ?

resp, err := http.Get(url), vérifiez err, puis defer resp.Body.Close() et lisez le body avec io.ReadAll(resp.Body) ou décodez-le avec json.NewDecoder(resp.Body).Decode(&v). Vérifiez aussi resp.StatusCode : un 404 ou un 500 n'est pas une erreur pour http.Get.

Comment définir un timeout sur une requête HTTP en Go ?

Créez un client avec un timeout, client := &http.Client{Timeout: 10 * time.Second}, et utilisez-le à la place de http.Get ou de http.DefaultClient, qui n'ont pas de timeout global : un serveur qui accepte la connexion et ne répond jamais bloque l'appel indéfiniment. Pour une limite par requête, construisez la requête avec http.NewRequestWithContext et un context issu de context.WithTimeout.

Comment envoyer une requête POST avec un body JSON en Go ?

Sérialisez la valeur, puis envoyez-la : body, _ := json.Marshal(v) et http.Post(url, "application/json", bytes.NewReader(body)). Pour ajouter des en-têtes comme Authorization, construisez-la avec http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)), définissez req.Header.Set("Content-Type", "application/json"), et envoyez-la avec client.Do(req).

Pourquoi faut-il fermer resp.Body en Go ?

Le body est un flux sur une connexion ouverte. Le fermer rend la connexion au pool pour qu'elle soit réutilisée ; l'oublier fait fuir des connexions et des descripteurs de fichiers jusqu'à ce que le programme atteigne sa limite. Fermez-le même si vous ne le lisez pas, et lisez-le jusqu'au bout si vous voulez que la connexion soit réutilisée.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER