Что такое 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);
};

Ответ 18+ 🔞

О, а вот это, блядь, Server-Sent Events, SSE! Ну, технология, которая позволяет серверу, сука, писать клиенту в ухо, как радио, в одностороннем порядке. Одно HTTP-соединение, и всё, пиздец, понеслась. Не то что эти ваши WebSockets с их двусторонней болтовнёй — тут всё проще, на обычном HTTP, но клиент, понятное дело, ответить не может, только слушать, как идиот.

Что за зверь такой, этот SSE:

  • Однорукий бандит: Данные летят только в одну сторону — от сервера к клиенту. Сервер -> клиент, и всё, блядь. Обратно — ни хуя.
  • Проще пареной репы: Реализовать — не вставая с дивана. На сервере — заголовки поправить, на клиенте — стандартный EventSource в браузере.
  • Самовосстанавливающийся: Если соединение отвалилось, браузерный клиент сам, сука, попытается переподключиться, как назойливая муха. Удобно, блядь.

Куда это воткнуть: Идеально, ёпта, для ситуаций, когда клиенту нужно только получать пинки, а не отвечать. Например:

  • Лента новостей, которая сама обновляется, пока ты кофе пьёшь.
  • Уведомления, которые выскакивают, как чёртики из табакерки.
  • Прогресс-бар для долгой задачи, типа "рендерим видео, сиди, жди, не дыши".
  • Дашборды с биржевыми котировками, где цифры пляшут, как угорелые.

Как это выглядит на Go, ёпта

Серверу нужно надеть правильную шапку и слать события в строгом формате: data: <сообщение>nn. Два переноса строки, блядь, не один, а то клиент обосрётся и ничего не поймёт.

func sseHandler(w http.ResponseWriter, r *http.Request) {
    // Надеваем на ответ правильные заголовки, как парадную форму
    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), там вообще красота

// Подключаемся к нашему эндпоинту, как к пожарной сирене
const eventSource = new EventSource('/sse');

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

// А тут ловим всякие косяки — обрыв связи и прочую хуйню
eventSource.onerror = (err) => {
  console.error("EventSource failed:", err);
};

Вот и вся магия, блядь. Клиент сидит, слушает, сервер его поливает данными, как из брандспойта. Красота, в рот меня чих-пых!