Konrad Kowalski (rootsher)Principal Platform & Reliability Architect110001100101010111100011000101110111000011110100

Server-Sent Events: protokół zdarzeń nad HTTP streamingiem

data
kategoria
Frontend
także w
Backend · Networking
czytanie
2 min / 326 słów

Raw HTTP streaming daje nam:

text
response body -> bytes over time

Ale bytes nie mają jeszcze semantyki zdarzeń.

Server-Sent Events dodaje standardowy format oraz browser API.

Event stream

Serwer odpowiada:

http
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

i pozostawia response otwarty.

Dane mają prosty framing:

text
id: 1843
event: order-status
data: {"id":42,"status":"completed"}

id: 1844
event: notification
data: {"message":"ready"}

Pusta linia kończy event.

Browser może użyć EventSource:

js
const source = new EventSource("/events");

source.addEventListener(
  "order-status",
  event => {
    const order = JSON.parse(event.data);
    render(order);
  }
);

SSE jest jednokierunkowe

Model komunikacji:

text
client -> HTTP request
server -> event
server -> event
server -> event

Aplikacyjne dane płyną z serwera do klienta.

Jeżeli klient musi coś wysłać, robi normalny request:

text
POST /messages
GET  /events   <- SSE stream

To nie jest wada, jeśli domena naturalnie ma taki kierunek.

Przykład:

text
client -> start report generation
server -> progress 10%
server -> progress 40%
server -> progress 100%

Nie potrzebujemy full-duplex protocol tylko dlatego, że dane przychodzą „na żywo”.

Reconnect jest częścią mechanizmu

EventSource automatycznie próbuje ponownie połączyć się po utracie connection.

Serwer może wysłać:

text
retry: 3000

aby zasugerować czas reconnectu w milisekundach.

Jeszcze ważniejsze jest:

text
id: 1844

Browser zapamiętuje ID ostatniego zdarzenia.

Przy reconnect może przekazać:

http
Last-Event-ID: 1844

Backend może wznowić stream od kolejnego eventu.

To nie zapewnia automatycznie exactly-once delivery.

Daje jednak standardowy mechanizm do implementacji resume.

Event ID wymusza pytanie o historię

Jeżeli klient wraca z:

text
Last-Event-ID: 1844

backend musi odpowiedzieć na pytanie:

czy nadal potrafię odtworzyć eventy od 1845?

Możliwe modele:

text
in-memory buffer
database/event log
message broker retention
no replay support

SSE rozwiązuje transport eventów do browsera.

Nie rozwiązuje trwałości eventów w systemie.

Connection limits i HTTP version

Przy HTTP/1.1 przeglądarki mają niski limit równoległych połączeń na origin, co może być problematyczne przy wielu kartach i wielu SSE streams.

Przy HTTP/2 wiele strumieni aplikacyjnych może współdzielić jedno połączenie HTTP/2, a limity są negocjowane inaczej.

To dobry przykład, dlaczego:

text
application stream
!=
transport connection

Proxy może zepsuć „realtime”

Backend może flushować event natychmiast, ale reverse proxy może buforować response.

Wtedy architektura wygląda poprawnie:

text
backend -> event -> proxy -> browser

ale proxy trzyma dane, aż zbierze większy bufor.

Dlatego przy streamingu trzeba sprawdzić:

  • proxy buffering,
  • compression buffering,
  • idle timeout,
  • connection draining,
  • CDN behavior.

Realtime jest właściwością całej ścieżki, nie samego endpointu.

SSE czy WebSocket?

SSE jest bardzo dobrym wyborem, gdy:

text
dominant direction = server -> client

Na przykład:

  • notifications,
  • build/deploy status,
  • observability feeds,
  • report progress,
  • AI output,
  • live dashboards.

Ale istnieją systemy, gdzie obie strony wysyłają wiadomości często i niezależnie:

text
client -> cursor position
server -> remote cursor position

client -> game input
server -> world state

Wtedy dwa osobne kanały HTTP zaczynają być sztuczne.

Potrzebujemy trwałego kanału full-duplex.

To jest WebSocket.