Komunikacja w czasie rzeczywistym w przeglądarce

Klasyczny model HTTP to żądanie-odpowiedź: klient pyta, serwer odpowiada. Ale co, gdy serwer chce poinformować klienta o czymś bez pytania? Powiadomienia, czat na żywo, aktualizacje statusu, dane giełdowe — wszystkie te scenariusze wymagają komunikacji w czasie rzeczywistym.

Dwa główne podejścia to WebSockets i Server-Sent Events (SSE). Oba rozwiązują problem "serwer chce wysłać dane do klienta", ale robią to w fundamentalnie różny sposób. Wybór między nimi ma konkretne konsekwencje dla architektury, wydajności i złożoności aplikacji.

WebSockets — pełny duplex

WebSocket to protokół ustanawiający stałe, dwukierunkowe połączenie TCP między klientem a serwerem. Po początkowym handshake'u (upgrade z HTTP) obie strony mogą wysyłać dane w dowolnym momencie, bez narzutu nagłówków HTTP.

Klient w JavaScript:

const ws = new WebSocket('wss://example.com/ws');

ws.onopen = () => ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
ws.onmessage = (event) => console.log(JSON.parse(event.data));
ws.onclose = () => console.log('Rozłączono');

Serwer w Go (z pakietem gorilla/websocket lub standardowej biblioteki od Go 1.22):

func handleWS(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer conn.Close()

    for {
        _, msg, err := conn.ReadMessage()
        if err != nil {
            break
        }
        // przetwórz wiadomość i odpowiedz
        conn.WriteMessage(websocket.TextMessage, response)
    }
}

Zalety WebSockets:

  • Dwukierunkowa komunikacja — klient i serwer wysyłają dane swobodnie
  • Niski narzut — po handshake'u ramki danych mają minimalny overhead (2-14 bajtów)
  • Binarny i tekstowy — obsługa obu typów danych
  • Niskie opóźnienia — brak narzutu HTTP na każdą wiadomość

Wady:

  • Złożoność — zarządzanie połączeniami, heartbeat, reconnect, stany błędów
  • Problemy z proxy — starsze proxy HTTP i firewalle mogą blokować WebSockety
  • Skalowalność — każde połączenie to stały koszt pamięci na serwerze
  • Brak automatycznego reconnectu — trzeba implementować samodzielnie

Server-Sent Events — prostota jednokierunkowa

SSE to mechanizm wbudowany w standard HTTP, pozwalający serwerowi wysyłać strumień zdarzeń do klienta. Komunikacja jest jednokierunkowa — tylko serwer do klienta. Klient komunikuje się z serwerem normalnym HTTP (POST, PUT, etc.).

Klient w JavaScript:

const source = new EventSource('/events');

source.onmessage = (event) => console.log(event.data);
source.addEventListener('notification', (event) => {
    console.log('Powiadomienie:', JSON.parse(event.data));
});

Serwer w Go:

func handleSSE(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")

    flusher, _ := w.(http.Flusher)

    for {
        select {
        case <-r.Context().Done():
            return
        case msg := <-messages:
            fmt.Fprintf(w, "event: notification\ndata: %s\n\n", msg)
            flusher.Flush()
        }
    }
}

Zalety SSE:

  • Prostota — działa na zwykłym HTTP, nie wymaga specjalnego protokołu
  • Automatyczny reconnect — przeglądarka sama wznawia połączenie po zerwaniu
  • Event ID — wbudowany mechanizm śledzenia ostatniego odebranego zdarzenia, pozwalający wznowić strumień od miejsca przerwania
  • Kompatybilność — działa przez każdy proxy HTTP, firewall, load balancer
  • Standardowa biblioteka Go — nie potrzeba zewnętrznych pakietów

Wady:

  • Jednokierunkowość — tylko serwer do klienta
  • Tylko tekst — brak wsparcia dla danych binarnych (trzeba kodować w Base64)
  • Limit połączeń — przeglądarki ograniczają liczbę jednoczesnych połączeń SSE per domena (zwykle 6 w HTTP/1.1, bez limitu w HTTP/2)
  • Brak w IE — Internet Explorer nie obsługuje SSE (ale to już mało istotne w 2025 roku)

Kiedy wybrać WebSockets

WebSockets to właściwy wybór, gdy potrzebujesz dwukierunkowej komunikacji w czasie rzeczywistym:

  • Czat — użytkownicy wysyłają i odbierają wiadomości
  • Gry multiplayer — ciągła wymiana stanu gry
  • Współedycja dokumentów — np. Google Docs, gdzie wielu użytkowników edytuje jednocześnie
  • Terminale webowe — interaktywna sesja z serwerem
  • Streaming binarny — przesyłanie audio/video

Reguła kciuka: jeśli klient musi wysyłać dane do serwera z tą samą częstotliwością co serwer do klienta — wybierz WebSockets.

Kiedy wybrać SSE

SSE jest lepszym wyborem, gdy komunikacja jest głównie jednokierunkowa (serwer do klienta):

  • Powiadomienia — nowe wiadomości, alerty, statusy
  • Dashboardy — aktualizacje metryk w czasie rzeczywistym
  • Feedy aktywności — nowe posty, komentarze, logi
  • Postęp operacji — pasek postępu długo trwającego zadania
  • Aktualizacje cenowe — kursy walut, ceny akcji (jednokierunkowy strumień)

Jeśli klient rzadko wysyła dane (i może to robić zwykłym POST/PUT) — SSE jest prostszym i bardziej niezawodnym rozwiązaniem.

Porównanie praktyczne

| Cecha | WebSockets | SSE | |-------|-----------|-----| | Kierunek | Dwukierunkowy | Serwer → klient | | Protokół | ws:// / wss:// | HTTP | | Reconnect | Ręczny | Automatyczny | | Dane binarne | Tak | Nie (Base64) | | Proxy/firewall | Problematyczne | Bez problemów | | Złożoność serwera | Wyższa | Niższa | | Zależności w Go | Zewnętrzny pakiet* | Standardowa biblioteka |

*Od Go 1.22 pakiet net/http zawiera eksperymentalne wsparcie dla WebSockets, ale w produkcji nadal częściej stosuje się gorilla/websocket lub nhooyr/websocket.

Wzorzec hybrydowy

W praktyce wiele aplikacji łączy oba podejścia. SSE do powiadomień push (serwer informuje klienta o zmianach), zwykłe HTTP POST/PUT do akcji użytkownika. Ten wzorzec jest prostszy w implementacji i debugowaniu niż pełny WebSocket, a wystarcza dla większości przypadków.

Przykład: system zarządzania zadaniami. Użytkownik tworzy zadanie (POST), zmienia status (PUT), a SSE informuje wszystkich o aktualizacjach w czasie rzeczywistym. Brak potrzeby WebSocketów — komunikacja klient-serwer jest sporadyczna i nieinteraktywna.

Podsumowanie

WebSockets i SSE to nie konkurencyjne technologie, lecz narzędzia do różnych zastosowań. SSE powinno być domyślnym wyborem — jest prostsze, niezawodniejsze i nie wymaga zewnętrznych bibliotek w Go. Po WebSockets sięgaj, gdy naprawdę potrzebujesz dwukierunkowej komunikacji z niskim opóźnieniem.

Planujesz aplikację z komunikacją w czasie rzeczywistym? Skontaktuj się z nami — pomożemy wybrać odpowiednie podejście.