Introduction aux événements envoyés par le serveur (SSE)

Les événements envoyés par le serveur (SSE) sont une technologie de push côté serveur qui permet aux clients de recevoir automatiquement des mises à jour du serveur via une connexion HTTP. Elle décrit comment le serveur initie le transfert de données au client après avoir établi la connexion initiale avec le client. Ils sont couramment utilisés pour envoyer des mises à jour de messages ou des flux de données continus aux clients du navigateur, dans le but d'améliorer les flux locaux inter-navigateurs via l'API JavaScript appelée EventSource. Les clients peuvent utiliser cette API pour demander une URL spécifique afin de recevoir des flux d'événements. Dans le cadre de HTML5, l'API EventSource a été normalisée par WHATWG. Le type de média pour SSE est text/event-stream.

Remarque : La plus grande différence entre SSE et Websocket est que SSE est un push de messages unidirectionnel du serveur vers le client, tandis que Websocket est un push bidirectionnel de messages. Parfois, lorsque les exigences métier ne sont pas aussi complexes, le push unidirectionnel de messages de SSE est suffisant. Ceci est similaire à la technologie utilisée dans les conversations ChatGPT de l'IA.

Exemple de SSE de Fiber

package main

import (
	"bufio"
	"fmt"
	"log"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/valyala/fasthttp"
)

// Simuler un client HTML, voici un exemple de réception de messages push côté backend en JS. Dans des scénarios métier réels, le frontend implémentera ceci.
var index = []byte(`<!DOCTYPE html>
<html>
<body>

<h1>Messages SSE</h1>
<div id="result"></div>

<script>
if(typeof(EventSource) !== "undefined") {
  var source = new EventSource("http://127.0.0.1:3000/sse");
  source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
  };
} else {
  document.getElementById("result").innerHTML = "Désolé, votre navigateur ne prend pas en charge les événements envoyés par le serveur...";
}
</script>

</body>
</html>
`)

func main() {
	// Instance de Fiber
	app := fiber.New()

	// Restrictions CORS, permettant l'accès depuis n'importe quel domaine
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "*",
		AllowHeaders:     "Cache-Control",
		AllowCredentials: true,
	}))

	// Accéder au chemin /, nous retournons d'abord la page frontend, permettant au frontend de demander au backend de démarrer le push de messages SSE
	app.Get("/", func(c *fiber.Ctx) error {
		c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)

		return c.Status(fiber.StatusOK).Send(index)
	})

	// Adresse de push de messages SSE
	app.Get("/sse", func(c *fiber.Ctx) error {
		// Définir l'en-tête http SSE, attention à Content-Type
		c.Set("Content-Type", "text/event-stream")
		c.Set("Cache-Control", "no-cache")
		c.Set("Connection", "keep-alive")
		c.Set("Transfer-Encoding", "chunked")

		// Commencer à pousser des messages
		c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
			fmt.Println("ÉCRIVAIN")
			var i int
			// Simuler le push continu de messages vers le client
			for {
				i++
				msg := fmt.Sprintf("%d - l'heure est %v", i, time.Now())
				// Utiliser le writer et la fonction Fprintf pour pousser des messages vers le client, dans des scénarios métier réels, du texte JSON peut être envoyé pour la commodité du traitement par le frontend
				fmt.Fprintf(w, "data: Message: %s\n\n", msg)
				fmt.Println(msg)

				// Flush des données de sortie vers le client
				err := w.Flush()
				if err != nil {
					// Actualiser la page dans le navigateur web établira une nouvelle connexion SSE, mais seule la dernière reste active, donc les connexions inactives doivent être fermées ici.
					fmt.Printf("Erreur lors du flush : %v. Fermeture de la connexion http.\n", err)
					break
				}
				time.Sleep(2 * time.Second)
			}
		}))

		return nil
	})

	// Démarrer le serveur
	log.Fatal(app.Listen(":3000"))
}