Introdução às Server-Sent Events (SSE)

As Server-Sent Events (SSE) são uma tecnologia de push do servidor que permite aos clientes receber automaticamente atualizações do servidor por meio de uma conexão HTTP. Elas descrevem como o servidor inicia a transferência de dados para o cliente após estabelecer a conexão inicial com o cliente. Elas são comumente usadas para enviar atualizações de mensagens ou fluxos de dados contínuos para os clientes do navegador, com o objetivo de aprimorar os fluxos locais entre os navegadores por meio da API JavaScript chamada EventSource. Os clientes podem usar esta API para solicitar um URL específico para receber fluxos de eventos. Como parte do HTML5, a API EventSource foi padronizada pelo WHATWG. O tipo de mídia para SSE é text/event-stream.

Nota: A maior diferença entre SSE e Websocket é que SSE é um push de mensagens do servidor para o cliente em uma direção, enquanto o Websocket é um push de mensagens em duas direções. Às vezes, quando os requisitos de negócios não são tão complexos, o push de mensagens em uma direção do SSE é suficiente. Isso é semelhante à tecnologia utilizada nas conversas de IA do ChatGPT.

Exemplo de SSE do Fiber

package main

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

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

// Simula o cliente HTML, aqui está um exemplo do frontend recebendo mensagens de push do backend em JS, em cenários de negócios reais, o frontend implementará isso.
var index = []byte(`<!DOCTYPE html>
<html>
<body>

<h1>Mensagens 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 = "Desculpe, seu navegador não suporta eventos enviados pelo servidor...";
}
</script>

</body>
</html>
`)

func main() {
	// Instância do Fiber
	app := fiber.New()

	// Restrições do CORS, permitindo acesso de qualquer domínio
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "*",
		AllowHeaders:     "Cache-Control",
		AllowCredentials: true,
	}))

	// Acessa o caminho /, primeiro retornamos a página frontend, permitindo que o frontend solicite ao backend para iniciar o push de mensagens do SSE
	app.Get("/", func(c *fiber.Ctx) error {
		c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)

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

	// Endereço de push de mensagens sse
	app.Get("/sse", func(c *fiber.Ctx) error {
		// Define o cabeçalho http sse, preste atenção a 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")

		// Inicia o push de mensagens
		c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
			fmt.Println("ESCRITOR")
			var i int
			// Simula o push contínuo de mensagens para o cliente
			for {
				i++
				msg := fmt.Sprintf("%d - a hora é %v", i, time.Now())
				// Usa o escritor e a função Fprintf para empurrar mensagens para o cliente, em cenários de negócios reais, um texto JSON pode ser enviado para conveniência de processamento frontend
				fmt.Fprintf(w, "data: Mensagem: %s\n\n", msg)
				fmt.Println(msg)

				// Libera os dados de saída para o cliente
				err := w.Flush()
				if err != nil {
					// Atualizar a página no navegador estabelecerá uma nova conexão SSE, mas apenas a última estará ativa, então as conexões inativas devem ser fechadas aqui.
					fmt.Printf("Erro ao liberar: %v. Fechando conexão http.\n", err)
					break
				}
				time.Sleep(2 * time.Second)
			}
		}))

		return nil
	})

	// Inicia o servidor
	log.Fatal(app.Listen(":3000"))
}