Что такое Server-Sent Events (SSE) и в каких сценариях их стоит применять? Приведите пример реализации на Go.

Ответ

Да, я знаком с Server-Sent Events (SSE). Это технология, которая позволяет серверу отправлять обновления клиенту в одностороннем порядке через одно HTTP-соединение. В отличие от WebSockets, SSE проще и работает поверх стандартного HTTP, но не поддерживает двунаправленную связь.

Ключевые особенности SSE:

  • Однонаправленная связь: Только сервер может отправлять данные клиенту (server -> client).
  • Простота: Легко реализуется как на сервере, так и на клиенте (стандартный EventSource API в браузерах).
  • Автоматическое переподключение: Браузерный клиент автоматически пытается восстановить соединение в случае обрыва.

Сценарии применения:
SSE идеально подходит для задач, где не требуется ответ от клиента, например:

  • Лента новостей или обновлений в реальном времени.
  • Системы уведомлений.
  • Отображение статуса выполнения долгой задачи (например, обработка видео).
  • Live-дэшборды с котировками или метриками.

Пример реализации на Go

Сервер должен установить специальные заголовки и отправлять события в формате data: <сообщение>nn.

func sseHandler(w http.ResponseWriter, r *http.Request) {
    // Устанавливаем необходимые заголовки для SSE
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    // http.Flusher необходим для отправки данных клиенту по частям,
    // а не после завершения всего ответа.
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
        return
    }

    // Бесконечный цикл для отправки событий
    for {
        select {
        // Если клиент закрыл соединение, r.Context().Done() получит сигнал
        case <-r.Context().Done():
            fmt.Println("Client disconnected")
            return
        // Отправляем событие каждую секунду
        default:
            // Формат события: "data: {message}nn"
            fmt.Fprintf(w, "data: Текущее время: %snn", time.Now().Format(time.RFC1123))
            flusher.Flush() // Отправляем буферизированные данные клиенту
            time.Sleep(1 * time.Second)
        }
    }
}

Клиентская часть (JavaScript)

// Создаем подключение к нашему SSE-эндпоинту
const eventSource = new EventSource('/sse');

// Обработчик для всех сообщений без указанного event
eventSource.onmessage = (event) => {
  console.log('Новое сообщение:', event.data);
};

// Обработчик ошибок
eventSource.onerror = (err) => {
  console.error("EventSource failed:", err);
};