Pourquoi WebSockets avec Go ?
WebSockets est une technologie qui permet aux navigateurs et aux serveurs d'établir des connexions persistantes sur lesquelles ils peuvent échanger des données en temps réel. Dans un contexte professionnel, cela peut être utile pour de nombreux cas d'usage, comme le développement d'applications de chat en direct, la mise à jour en temps réel de l'état d'une application, ou encore la création d'applications multijoueurs.
Un cas concret serait une application de chat en direct. Avec un WebSocket, chaque utilisateur peut se connecter au serveur et recevoir des mises à jour en temps réel dès qu'un autre utilisateur envoie un message. Sans WebSocket, chaque message devrait être traité individuellement et le navigateur doit faire plusieurs requêtes pour obtenir les dernières informations.
Prerequis
- Connaissances du langage Go
- Connaissance de l'écosystème Go (Go modules, etc.)
- Connaissance des concepts de base en programmation asynchrone
- Une installation récente de Go (1.18 ou plus)
- Un serveur web comme
net/http - L'outil
go runpour exécuter rapidement du code
Concepts fondamentaux
Connexion WebSocket
Un WebSocket est établi entre un client et un serveur à l'aide d'une requête HTTP. Le protocole de connexion est basé sur le protocole HTTP, mais une négociation supplémentaire est effectuée pour passer au protocole WebSocket.
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
http.HandleFunc("/echo", echo)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Envoi et réception de messages
Une fois la connexion établie, le client peut envoyer des messages au serveur et recevoir des réponses. Dans l'exemple ci-dessus, chaque message reçu est envoyé de nouveau au client.
Gestion des erreurs
La gestion des erreurs est essentielle pour assurer une bonne communication. Voici un exemple de gestion d'erreur lors de la lecture d'un message :
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
Différences entre HTTP et WebSocket
- HTTP est un protocole request-response. Une requête HTTP peut être envoyée et une réponse reçue, mais la connexion est fermée après l'échange.
- WebSocket est un protocole bidirectionnel qui permet de garder la connexion ouverte pour échanger des données en temps réel.
Mise en pratique : projet fil rouge
Nous allons créer un simple application de chat en direct utilisant WebSockets. L'application sera basée sur le framework Go et nous utiliserons gorilla/websocket pour gérer les connexions WebSocket.
Étape 1 : Créer une nouvelle application Go
Créez un nouveau fichier main.go :
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
http.HandleFunc("/echo", echo)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Étape 2 : Créer un template HTML pour le chat
Créez un nouveau fichier index.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat WebSocket</title>
</head>
<body>
<h1>Chat WebSocket</h1>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Type a message...">
<button onclick="sendMessage()">Send</button>
<script>
const socket = new WebSocket("ws://localhost:8080/echo");
socket.onopen = function() {
console.log("WebSocket connection established");
};
socket.onmessage = function(event) {
const messagesDiv = document.getElementById('messages');
const message = document.createElement('div');
message.textContent = event.data;
messagesDiv.appendChild(message);
};
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value;
socket.send(message);
input.value = '';
}
</script>
</body>
</html>
Étape 3 : Ajouter le template HTML à votre application Go
Modifiez main.go pour servir le fichier index.html :
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{} // use default options
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func serveHTML(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
}
func main() {
http.HandleFunc("/echo", echo)
http.HandleFunc("/", serveHTML)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Étape 4 : Exécuter l'application
Assurez-vous que vous avez le répertoire du projet dans votre GOPATH ou utilisez Go modules :
go mod init chatapp
go get github.com/gorilla/websocket
go run main.go
Ouvrez un navigateur et accédez à http://localhost:8080. Vous devriez voir une interface de chat simple où vous pouvez envoyer et recevoir des messages.
Erreurs frequentes et debugging
Erreur 1 : Connection refused
## ❌ Mauvais
go run main.go
Message d'erreur :
listen tcp :8080: listen: address already in use
Code corrige :
## 🔄 Corriger l'adresse IP ou le port
netstat -tuln | grep 8080
kill <PID>
go run main.go
Erreur 2 : Invalid argument
## ❌ Mauvais
upgrader := websocket.Upgrader{}
Message d'erreur :
panic: invalid argument to net.ListenUnixgram
Code corrige :
## 🔄 Utiliser une adresse IP valide
upgrader := websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
Erreur 3 : Unexpected EOF
## ❌ Mauvais
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
Message d'erreur :
read: unexpected EOF
Code corrige :
## 🔄 Vérifier que le client n'a pas fermé la connexion
messageType, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
log.Println("Client closed connection")
} else {
log.Println("read:", err)
}
break
}
Pour aller plus loin
Piste 1 : Authentification et sécurité
Ajoutez une couche d'authentification et de sécurité à votre application WebSocket. Utilisez des jetons JWT pour authentifier les utilisateurs.
Piste 2 : Espace privé
Créez un espace privé où chaque utilisateur peut envoyer des messages à d'autres utilisateurs spécifiques.
Piste 3 : Gestion des groupes
Ajoutez la possibilité de créer et rejoindre des groupes. Les membres du même groupe peuvent échanger des messages entre eux.
Défi pratique
Créez une application de chat en direct avec plusieurs fonctionnalités supplémentaires, comme :
- Authentification via JWT
- Salles privées
- Notifications push pour les nouveaux messages
- Histoire des conversations