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:
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/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
i pozostawia response otwarty.
Dane mają prosty framing:
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:
const source = new EventSource("/events");
source.addEventListener(
"order-status",
event => {
const order = JSON.parse(event.data);
render(order);
}
);
SSE jest jednokierunkowe
Model komunikacji:
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:
POST /messages
GET /events <- SSE stream
To nie jest wada, jeśli domena naturalnie ma taki kierunek.
Przykład:
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ć:
retry: 3000
aby zasugerować czas reconnectu w milisekundach.
Jeszcze ważniejsze jest:
id: 1844
Browser zapamiętuje ID ostatniego zdarzenia.
Przy reconnect może przekazać:
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:
Last-Event-ID: 1844
backend musi odpowiedzieć na pytanie:
czy nadal potrafię odtworzyć eventy od 1845?
Możliwe modele:
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:
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:
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:
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:
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.