Введение в события, отправляемые сервером (SSE)
События, отправляемые сервером (SSE), представляют собой технологию серверного пуша, позволяющую клиентам автоматически получать обновления от сервера через HTTP-соединение. Они описывают, как сервер инициирует передачу данных клиенту после установки исходного клиентского соединения. Обычно они используются для отправки обновлений сообщений или непрерывных потоков данных клиентам браузера с целью улучшения локальных потоков через API JavaScript под названием EventSource. Клиенты могут использовать этот API для запроса определенного URL-адреса и получения потоков событий. В качестве части HTML5 API EventSource был стандартизирован WHATWG. Медиа-тип для SSE - text/event-stream.
Примечание: Самое большое различие между SSE и Websocket заключается в том, что SSE - это односторонний пуш сообщений от сервера к клиенту, в то время как Websocket - это двусторонний пуш сообщений. Иногда, когда бизнес-требования не такие сложные, достаточно одностороннего пуша сообщений SSE. Это аналогично используемой технологии в разговорах с искусственным интеллектом ChatGPT.
Пример SSE с Fiber
package main
import (
"bufio"
"fmt"
"log"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/valyala/fasthttp"
)
// Симуляция клиента HTML, вот пример получения фронтендом сообщений обратной отправки от бэкенда в JS, в реальных бизнес-сценариях это реализует фронтенд.
var index = []byte(`<!DOCTYPE html>
<html>
<body>
<h1>SSE Messages</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 = "Извините, ваш браузер не поддерживает события, отправляемые сервером (Server-Sent Events)...";
}
</script>
</body>
</html>
`)
func main() {
// Создание экземпляра Fiber
app := fiber.New()
// Ограничения CORS, разрешение доступа с любого домена
app.Use(cors.New(cors.Config{
AllowOrigins: "*",
AllowHeaders: "Cache-Control",
AllowCredentials: true,
}))
// При доступе к пути / сначала возвращаем страницу фронтенда, позволяя фронтенду запросить у бэкенда начать отправку сообщений SSE
app.Get("/", func(c *fiber.Ctx) error {
c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)
return c.Status(fiber.StatusOK).Send(index)
})
// Адрес отправки сообщений SSE
app.Get("/sse", func(c *fiber.Ctx) error {
// Установка HTTP-заголовков SSE, обратите внимание на 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")
// Начало отправки сообщений
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
fmt.Println("WRITER")
var i int
// Симуляция непрерывной отправки сообщений клиенту
for {
i++
msg := fmt.Sprintf("%d - время: %v", i, time.Now())
// Использование функции writer и Fprintf для отправки сообщений клиенту, в реальных бизнес-сценариях может быть отправлен текст JSON для удобства обработки фронтендом
fmt.Fprintf(w, "data: Сообщение: %s\n\n", msg)
fmt.Println(msg)
// Очистка выходных данных клиенту
err := w.Flush()
if err != nil {
// Обновление страницы в веб-браузере установит новое соединение SSE, но будет активно только последнее, поэтому здесь нужно закрыть неактивные соединения.
fmt.Printf("Ошибка при очистке: %v. Закрытие HTTP-соединения.\n", err)
break
}
time.Sleep(2 * time.Second)
}
}))
return nil
})
// Запуск сервера
log.Fatal(app.Listen(":3000"))
}