Envoyer du JSON dans une requête POST en Go : du "comment ça marche" au "fait et oublié"
Avez-vous déjà passé du temps sur Stack Overflow à essayer de comprendre pourquoi votre requête POST avec JSON en Go n'est pas acceptée par le serveur ? Ça vous dit quelque chose : tout semble correct, mais en réponse, vous obtenez une erreur 400 Bad Request ou un corps vide. Il s'avère qu'il existe plusieurs façons d'envoyer du JSON en Go, et choisir la bonne n'est pas seulement une question de goût, mais une question de stabilité pour votre microservice.
Dans cet article, nous ne nous contenterons pas de montrer du code. Nous analyserons trois approches : de la plus naïve (qui fonctionne mais est dangereuse) à une solution de production avec une gestion correcte des erreurs et des contextes. Si vous codez en Go pour des API, des bots ou des intégrations, cette lecture est indispensable.
Pourquoi ce sujet est-il si populaire ?
Go est un langage pour les services réseau. Tôt ou tard, tout développeur est confronté à la tâche : "envoyer du JSON dans le corps d'une requête POST". Cela semble simple, mais il y a des nuances :
- Il faut sérialiser correctement la structure en JSON (marshaling).
- Il est important de ne pas oublier l'en-tête
Content-Type: application/json. - Il faut fermer correctement le corps de la réponse.
- On souhaite éviter les fuites de mémoire et les paniques.
Analysons chaque approche avec des exemples concrets.
Approche 1 : "À la va-vite" — bytes.NewBuffer + json.Marshal
C'est la méthode la plus simple. On prend une structure, on la marshale en octets, on la met dans un buffer et on l'envoie. De nombreux développeurs Go débutants écrivent exactement comme ça.
package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type User struct { Name string `json:"name"` Email string `json:"email"`}
func main() { user := User{Name: "Иван Петров", Email: "ivan@example.com"} // Étape 1 : marshaling de la structure en JSON jsonData, err := json.Marshal(user) if err != nil { fmt.Println("Erreur de marshaling :", err) return } // Étape 2 : création de la requête POST resp, err := http.Post( "https://api.example.com/users", "application/json", bytes.NewBuffer(jsonData), ) if err != nil { fmt.Println("Erreur de requête :", err) return } defer resp.Body.Close() fmt.Println("Statut :", resp.Status)}Avantages et inconvénients de cette approche
- Avantage : Minimum de code, compréhensible pour un débutant.
- Inconvénient : Pas de flexibilité. Vous ne pouvez pas ajouter d'en-têtes personnalisés (authentification, trace-id). Pas de contrôle sur les timeouts. Si le serveur renvoie une erreur, vous ne la gérez pas.
- Inconvénient : Problème potentiel : si
json.Marshalrenvoie une erreur, vous l'attrapez, mais dans le code réel, on oublie souvent de vérifier.
Cette méthode convient pour les prototypes et les tests. Pour la production, non.
Approche 2 : "La bonne" — http.NewRequest + json.NewEncoder
La communauté Go recommande d'utiliser http.NewRequest et json.NewEncoder. Pourquoi ? Parce que cela donne un contrôle total sur la requête et est plus efficace en mémoire.
package main
import ( "bytes" "encoding/json" "fmt" "net/http" "time")
type Order struct { ID int `json:"id"` Amount float64 `json:"amount"` Items []string `json:"items"`}
func sendOrder(order Order) error { // Création du buffer et de l'encodeur var buf bytes.Buffer enc := json.NewEncoder(&buf) // Encodage directement dans le buffer (sans []byte intermédiaire) if err := enc.Encode(order); err != nil { return fmt.Errorf("erreur d'encodage JSON : %w", err) } // Création de la requête req, err := http.NewRequest("POST", "https://api.example.com/orders", &buf) if err != nil { return fmt.Errorf("erreur de création de la requête : %w", err) } // Définition des en-têtes req.Header.Set("Content-Type", "application/json") req.Hea