Contactez-nous

HTTP client et serveur

Créez des clients et serveurs HTTP robustes en Go. Explorez requêtes, réponses, gestionnaires, middleware, serveurs HTTPS et bonnes pratiques pour des applications web performantes.

Introduction aux clients et serveurs HTTP en Go : Le web à portée de main

Le protocole HTTP (Hypertext Transfer Protocol) est le protocole de communication fondamental du web. Comprendre et maîtriser la programmation HTTP client et serveur est essentiel pour le développement d'applications web modernes, d'APIs RESTful, de microservices, et de tout système communiquant via le web.

Go, avec sa bibliothèque standard net/http, offre des outils puissants et élégants pour construire des clients HTTP capables d'envoyer des requêtes HTTP à des serveurs web, et des serveurs HTTP capables de recevoir et de traiter des requêtes HTTP entrantes. La bibliothèque net/http de Go est réputée pour sa simplicité, sa performance, sa flexibilité et son approche idiomatique de la programmation web.

Ce chapitre vous propose un guide complet sur la création de clients et de serveurs HTTP en Go. Nous allons explorer en détail la construction de clients HTTP pour envoyer des requêtes (GET, POST, PUT, DELETE, etc.), la création de serveurs HTTP pour gérer les requêtes entrantes, la définition de gestionnaires (handlers) pour traiter les requêtes, l'utilisation de middleware pour ajouter des fonctionnalités transverses, la gestion des serveurs HTTPS pour les connexions sécurisées, et les bonnes pratiques pour le développement d'applications web robustes et performantes en Go. Que vous soyez débutant ou développeur web expérimenté, ce guide vous fournira les connaissances et les compétences nécessaires pour maîtriser la programmation HTTP avec Go et construire des applications web modernes et efficaces.

Clients HTTP en Go : Envoyer des requêtes et consommer des APIs web

En Go, la création de clients HTTP pour envoyer des requêtes vers des serveurs web et consommer des APIs RESTful est remarquablement simple et intuitive grâce au package net/http.

Le type http.Client : Le client HTTP principal

Le type http.Client est le type principal pour effectuer des requêtes HTTP en Go. Il représente un client HTTP et fournit des méthodes pour envoyer des requêtes et recevoir des réponses. Un http.Client peut être configuré avec différentes options (timeouts, transport, gestion des cookies, etc.) pour personnaliser le comportement du client.

Fonction http.Get(url string) (*http.Response, error) : Requêtes GET rapides

La fonction http.Get est une fonction utilitaire pratique pour effectuer rapidement des requêtes HTTP GET simples. Elle prend en argument l'URL de la ressource à récupérer et retourne une réponse HTTP *http.Response et une erreur error.

package main

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

func main() {
    url := "https://api.example.com/data"

    // Envoi d'une requête GET simple avec http.Get
    resp, err := http.Get(url)
    if err != nil {
        log.Fatalf("Erreur requête GET: %v", err)
        os.Exit(1)
    }
    defer resp.Body.Close() // Fermer le corps de la réponse après utilisation (important !)

    fmt.Println("Statut de la réponse :", resp.Status)     // Afficher le statut HTTP (200 OK, 404 Not Found, etc.)
    fmt.Println("Headers de la réponse :", resp.Header)   // Afficher les headers HTTP

    // Lecture du corps de la réponse (contenu de la page web, données JSON, etc.)
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("Erreur lecture du corps de la réponse: %v", err)
        os.Exit(1)
    }
    fmt.Println("Corps de la réponse :\n", string(body))
}

Création de requêtes HTTP personnalisées avec http.NewRequest et client.Do :

Pour effectuer des requêtes HTTP plus complexes (avec des méthodes HTTP différentes de GET, des headers personnalisés, un corps de requête, etc.), vous devez utiliser les fonctions http.NewRequest et client.Do.

  • http.NewRequest(method, url string, body io.Reader) (*http.Request, error) : Crée une nouvelle requête HTTP *http.Request. Vous spécifiez la méthode HTTP ("GET", "POST", "PUT", "DELETE", etc.), l'URL de la ressource, et un éventuel corps de requête io.Reader (pour les méthodes comme POST ou PUT qui envoient des données au serveur).
  • client.Do(req *http.Request) (*http.Response, error) : Envoie une requête HTTP *http.Request à un serveur et retourne la réponse HTTP *http.Response et une erreur error. Vous devez créer un http.Client (ou utiliser le client HTTP par défaut http.DefaultClient) et appeler la méthode Do sur ce client, en passant la requête *http.Request en argument.

Exemple de requête POST avec http.NewRequest et client.Do (envoi de données JSON) :

package main

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

func main() {
    url := "https://api.example.com/users"

    // Données JSON à envoyer dans le corps de la requête POST
    data := map[string]interface{}{
        "nom":     "Doe",
        "prenom":  "John",
        "email":   "john.doe@example.com",
    }
    payload, err := json.Marshal(data) // Sérialisation en JSON
    if err != nil {
        log.Fatalf("Erreur lors de la sérialisation JSON: %v", err)
        os.Exit(1)
    }

    // Création d'une requête POST personnalisée avec http.NewRequest
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) // Méthode POST, URL, corps de requête (io.Reader)
    if err != nil {
        log.Fatalf("Erreur lors de la création de la requête POST: %v", err)
        os.Exit(1)
    }

    // Ajouter des headers à la requête (Content-Type: application/json)
    req.Header.Set("Content-Type", "application/json")

    // Envoi de la requête avec client.Do (utilisation du client HTTP par défaut http.DefaultClient)
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatalf("Erreur lors de l'envoi de la requête POST: %v", err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    fmt.Println("Statut de la réponse POST :", resp.Status)
    // ... (traitement du corps de la réponse comme dans l'exemple précédent) ...
}

Ces exemples montrent comment créer des clients HTTP en Go, envoyer des requêtes GET et POST, personnaliser les requêtes (méthode, headers, corps), et traiter les réponses HTTP (statut, headers, corps). Le package net/http offre une API complète et flexible pour interagir avec les APIs web et les services HTTP en Go.

Serveurs HTTP en Go : Créer des APIs web et des services HTTP

La création de serveurs HTTP en Go est tout aussi simple et élégante qu'avec les clients HTTP, grâce au package net/http. Go facilite la mise en place de serveurs web, d'APIs RESTful, de microservices et d'autres applications HTTP.

Fonction http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)) : Définir les gestionnaires de requêtes

La fonction http.HandleFunc est essentielle pour définir les gestionnaires de requêtes (handlers) de votre serveur HTTP. Elle associe un chemin d'URL (pattern) à une fonction handler qui sera exécutée pour traiter les requêtes HTTP dont le chemin d'URL correspond au pattern spécifié.

Signature d'une fonction handler : func(ResponseWriter, *Request)

Une fonction handler en Go doit avoir la signature suivante : func(w http.ResponseWriter, r *http.Request).

  • http.ResponseWriter w : Interface pour écrire la réponse HTTP au client. Vous utilisez les méthodes de http.ResponseWriter (w.WriteHeader, w.Write, fmt.Fprintf(w, ...), etc.) pour construire et envoyer la réponse HTTP (statut, headers, corps).
  • *http.Request r : Pointeur vers une structure http.Request qui représente la requête HTTP reçue du client. Vous accédez aux informations de la requête via les champs de *http.Request (r.Method, r.URL, r.Header, r.Body, etc.).

Fonction http.ListenAndServe(adresse string, handler Handler) error : Lancer le serveur HTTP

La fonction http.ListenAndServe permet de lancer un serveur HTTP et de le mettre en écoute sur l'adresse spécifiée (adresse, au format "host:port"). Elle prend en arguments l'adresse d'écoute et un handler (gestionnaire de requêtes) à utiliser pour traiter les requêtes entrantes. Si le handler est nil, le serveur utilise le handler par défaut (http.DefaultServeMux). http.ListenAndServe est une fonction bloquante : elle démarre le serveur et bloque l'exécution du programme appelant jusqu'à ce que le serveur s'arrête (en cas d'erreur ou d'arrêt manuel).

Exemple de serveur HTTP simple en Go :

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

// Handler pour la route '/' (racine)
func handlerRacine(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Bienvenue sur mon serveur web Go !\n")
}

// Handler pour la route '/hello'
func handlerHello(w http.ResponseWriter, r *http.Request) {
    nom := r.URL.Query().Get("nom") // Récupérer le paramètre 'nom' de la requête (ex: /hello?nom=Alice)
    if nom == "" {
        nom = "Invité" // Valeur par défaut si le paramètre 'nom' est absent
    }
    fmt.Fprintf(w, "Bonjour, %s !\n", nom)
}

func main() {
    // Enregistrer les handlers pour différentes routes avec http.HandleFunc
    http.HandleFunc("/", handlerRacine)   // Route racine "/" gérée par 'handlerRacine'
    http.HandleFunc("/hello", handlerHello) // Route "/hello" gérée par 'handlerHello'

    adresseServeur := "localhost:8080" // Adresse d'écoute du serveur
    fmt.Println("Serveur HTTP en écoute sur", adresseServeur)
    err := http.ListenAndServe(adresseServeur, nil) // Lancement du serveur HTTP (handler par défaut : nil = http.DefaultServeMux)
    if err != nil {
        log.Fatalf("Erreur lors du lancement du serveur HTTP: %v", err)
        os.Exit(1)
    }
}

Cet exemple illustre la création d'un serveur HTTP simple en Go, avec deux routes ("/" et "/hello") et des handlers associés (handlerRacine et handlerHello). La fonction http.HandleFunc est utilisée pour enregistrer les handlers, et http.ListenAndServe lance le serveur HTTP et le met en écoute sur l'adresse spécifiée.

Middleware et routeurs : Structurer et étendre les serveurs HTTP

Pour construire des serveurs HTTP plus complexes et plus modulaires en Go, il est courant d'utiliser des middleware et des routeurs. Les middleware permettent d'ajouter des fonctionnalités transverses (logging, authentification, autorisation, compression, etc.) au traitement des requêtes HTTP, de manière réutilisable et modulaire. Les routeurs permettent de définir des routes plus sophistiquées et de gérer les requêtes de manière plus flexible et plus organisée.

Middleware HTTP : Intercepteurs de requêtes et de réponses

Un middleware HTTP est une fonction qui intercepte les requêtes HTTP entrantes avant qu'elles ne soient traitées par le handler final, et/ou qui intercepte les réponses HTTP sortantes avant qu'elles ne soient envoyées au client. Les middleware permettent d'ajouter des traitements additionnels aux requêtes et aux réponses, de manière transparente et réutilisable.

Structure d'un middleware HTTP :

Un middleware HTTP en Go est généralement une fonction qui prend en argument un http.Handler (le handler suivant dans la chaîne de middleware) et retourne un nouveau http.Handler (le middleware enveloppé). Le middleware enveloppé exécute son propre code (avant ou après l'appel au handler suivant) et appelle ensuite le handler suivant pour poursuivre le traitement de la requête.

Exemple de middleware HTTP (logging) :

package main

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

// Middleware de logging : Loggue chaque requête et sa durée
func MiddlewareLogging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        debut := time.Now()
        log.Printf("Début de la requête : %s %s\n", r.Method, r.URL.Path)
        next.ServeHTTP(w, r) // Appel au handler suivant dans la chaîne
        duree := time.Since(debut)
        log.Printf("Fin de la requête : %s %s, durée : %v\n", r.Method, r.URL.Path, duree)
    })
}

Routeurs HTTP : Organisation et multiplexage des routes

Un routeur HTTP (HTTP router ou mux) est un composant qui permet de mapper les requêtes HTTP entrantes (basées sur leur méthode HTTP et leur chemin d'URL) vers les handlers appropriés. Les routeurs offrent des fonctionnalités avancées de routage, comme :

  • Matching de routes basé sur des patterns complexes : Au-delà des routes statiques simples, les routeurs permettent de définir des routes avec des paramètres (variables dans l'URL), des wildcards (caractères génériques), des expressions régulières, etc.
  • Gestion des méthodes HTTP spécifiques : Les routeurs permettent de définir des handlers différents pour différentes méthodes HTTP (GET, POST, PUT, DELETE) pour une même route (par exemple, /users avec GET pour lister les utilisateurs, POST pour créer un utilisateur, etc.).
  • Groupement de routes (grouping) : Les routeurs permettent de grouper des routes sous un préfixe commun ou un middleware commun, facilitant l'organisation et la gestion des routes pour les applications web complexes.
  • Middleware au niveau du routeur ou des groupes de routes : Les routeurs permettent d'appliquer des middleware à l'ensemble du routeur, à des groupes de routes, ou à des routes spécifiques, offrant un contrôle fin sur l'application des middleware.

Frameworks web populaires avec routeurs et middleware :

De nombreux frameworks web populaires en Go (comme Gin, Echo, Fiber, Chi, Gorilla Mux, etc.) fournissent des routeurs HTTP puissants et des mécanismes de middleware intégrés, simplifiant considérablement la construction de serveurs HTTP complexes et modulaires. L'utilisation d'un framework web est fortement recommandée pour les applications web Go de taille conséquente.

Serveurs HTTPS : Sécuriser les communications web

Pour sécuriser les communications web et protéger les données sensibles transitant entre le client et le serveur, il est essentiel d'utiliser HTTPS (HTTP Secure), la version sécurisée de HTTP qui chiffre les communications via le protocole TLS/SSL.

Création de serveurs HTTPS en Go : http.ListenAndServeTLS

En Go, la création de serveurs HTTPS est aussi simple que la création de serveurs HTTP classiques. Il suffit d'utiliser la fonction http.ListenAndServeTLS(adresse string, certFile string, keyFile string, handler Handler) error au lieu de http.ListenAndServe.

Fonction http.ListenAndServeTLS : Lancer un serveur HTTPS

La fonction http.ListenAndServeTLS lance un serveur HTTPS et le met en écoute sur l'adresse spécifiée (adresse, au format "host:port"), en utilisant le protocole HTTPS/TLS pour sécuriser les communications. Elle prend en arguments :

  • adresse string : L'adresse d'écoute du serveur HTTPS (au format "host:port").
  • certFile string : Le chemin vers le fichier contenant le certificat SSL/TLS du serveur (au format PEM). Le certificat SSL/TLS est utilisé pour prouver l'identité du serveur et pour établir une connexion chiffrée avec le client.
  • keyFile string : Le chemin vers le fichier contenant la clé privée SSL/TLS du serveur (au format PEM). La clé privée est utilisée pour le déchiffrement des communications chiffrées.
  • handler Handler : Le handler (gestionnaire de requêtes) à utiliser pour traiter les requêtes HTTPS entrantes (comme pour http.ListenAndServe).

Génération de certificats SSL/TLS pour les tests locaux (mkcert) :

Pour tester un serveur HTTPS en environnement local (développement, tests), vous pouvez générer des certificats SSL/TLS auto-signés (non valides pour les navigateurs web en production, mais suffisants pour les tests locaux). L'outil mkcert (disponible sur [https://mkcert.dev/](https://mkcert.dev/)) est un outil pratique pour générer facilement des certificats locaux auto-signés pour HTTPS.

Exemple de serveur HTTPS simple en Go (avec certificats auto-signés) :

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

// ... (Handlers handlerRacine et handlerHello comme dans l'exemple précédent) ...

func main() {
    http.HandleFunc("/", handlerRacine)
    http.HandleFunc("/hello", handlerHello)

    adresseServeurHTTPS := "localhost:8443" // Adresse d'écoute HTTPS (port 443 par convention, ici 8443 pour éviter les droits root)
    certFile := "cert.pem"                  // Chemin vers le fichier de certificat SSL/TLS (auto-signé)
    keyFile := "key.pem"                    // Chemin vers le fichier de clé privée SSL/TLS (auto-signée)

    fmt.Println("Serveur HTTPS en écoute sur", adresseServeurHTTPS)
    err := http.ListenAndServeTLS(adresseServeurHTTPS, certFile, keyFile, nil) // Lancement du serveur HTTPS avec ListenAndServeTLS
    if err != nil {
        log.Fatalf("Erreur lors du lancement du serveur HTTPS: %v", err)
        os.Exit(1)
    }
}

Génération des fichiers cert.pem et key.pem avec mkcert (pour les tests locaux) :

mkcert localhost

Cette commande mkcert localhost va générer deux fichiers dans le répertoire courant : cert.pem (le certificat auto-signé) et key.pem (la clé privée auto-signée). Vous pouvez utiliser ces fichiers pour tester votre serveur HTTPS en local.

Attention : Certificats valides pour la production (autorité de certification)

Pour les serveurs HTTPS en production, il est crucial d'utiliser des certificats SSL/TLS valides, signés par une autorité de certification (CA) reconnue par les navigateurs web et les clients HTTP. Les certificats auto-signés ne sont pas valides pour la production et généreront des avertissements de sécurité dans les navigateurs web. Obtenez un certificat SSL/TLS valide auprès d'une autorité de certification (comme Let's Encrypt, Comodo, DigiCert, etc.) et utilisez les fichiers de certificat et de clé privée correspondants avec http.ListenAndServeTLS pour sécuriser correctement votre serveur HTTPS en production.