Wprowadzenie do wydarzeń wysyłanych z serwera (SSE)

Wydarzenia wysyłane z serwera (SSE) to technologia pushowania z serwera, która umożliwia klientom automatyczne otrzymywanie aktualizacji ze strony serwera za pośrednictwem połączenia HTTP. Opisuje, jak serwer inicjuje transfer danych do klienta po ustanowieniu początkowego połączenia klienta. Są one często używane do wysyłania aktualizacji wiadomości lub ciągłych strumieni danych do klientów przeglądarek, w celu wzbogacenia lokalnych strumieni międzyprzeglądarkowych za pomocą interfejsu API JavaScript o nazwie EventSource. Klienci mogą używać tego interfejsu API do żądania określonego adresu URL w celu otrzymywania strumieni zdarzeń. Jako część HTML5, interfejs API EventSource został uzgodniony przez WHATWG. Typ nośnika dla SSE to text/event-stream.

Uwaga: Największą różnicą między SSE a Websocket jest to, że SSE jest jednokierunkowym przesyłaniem wiadomości z serwera do klienta, podczas gdy Websocket jest dwukierunkowym przesyłaniem wiadomości. Czasami, gdy wymagania biznesowe nie są tak złożone, wystarczające jest jednokierunkowe przesyłanie wiadomości SSE. Jest to podobne do technologii używanej w rozmowach AI ChatGPT.

Przykład Fiber SSE

package main

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

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

// Symulowanie klienta HTML, oto przykład otrzymywania przesyłanych wiadomości z backendu w JS, w rzeczywistych scenariuszach biznesowych frontend będzie to implementował.
var index = []byte(`<!DOCTYPE html>
<html>
<body>

<h1>Wiadomości 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 = "Przepraszamy, Twoja przeglądarka nie obsługuje wydarzeń wysyłanych z serwera...";
}
</script>

</body>
</html>
`)

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

	// Ograniczenia CORS, umożliwiające dostęp z dowolnej domeny
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "*",
		AllowHeaders:     "Cache-Control",
		AllowCredentials: true,
	}))

	// Dostęp do ścieżki /, najpierw zwracamy stronę frontendową, umożliwiając frontendowi żądanie od backendu rozpoczęcia przesyłania wiadomości SSE
	app.Get("/", func(c *fiber.Ctx) error {
		c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)

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

	// Adres przesyłania wiadomości SSE
	app.Get("/sse", func(c *fiber.Ctx) error {
		// Ustaw nagłówek http SSE, zwróć uwagę na 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")

		// Rozpocznij przesyłanie wiadomości
		c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
			fmt.Println("WRITER")
			var i int
			// Symuluj ciągłe przesyłanie wiadomości do klienta
			for {
				i++
				msg := fmt.Sprintf("%d - czas: %v", i, time.Now())
				// Użyj funkcji pisarza i Fprintf do przesyłania wiadomości do klienta, w rzeczywistych scenariuszach biznesowych można wysłać tekst JSON dla wygody przetwarzania frontendu
				fmt.Fprintf(w, "data: Wiadomość: %s\n\n", msg)
				fmt.Println(msg)

				// Spłucz dane wyjściowe do klienta
				err := w.Flush()
				if err != nil {
					// Odświeżanie strony w przeglądarce internetowej spowoduje ustanowienie nowego połączenia SSE, ale tylko ostatnie będzie aktywne, więc martwe połączenia muszą być tutaj zamykane.
					fmt.Printf("Błąd podczas spłukiwania: %v. Zamykanie połączenia http.\n", err)
					break
				}
				time.Sleep(2 * time.Second)
			}
		}))

		return nil
	})

	// Uruchom serwer
	log.Fatal(app.Listen(":3000"))
}