Server Sent Events (SSE) jest mechanizmem, który pozwala na jednokierunkową komunikację pomiędzy serwerem a klientem. Możemy przy jego pomocy wysyłać wiadomości i informować klienta o zmianach na serwerze. Brzmi znajomo? Najczęściej taka funkcjonalność kojarzy nam się z Websocket? Jaka jest pomiędzy nimi różnica, kiedy powinniśmy korzystać z SEE i jak z tego skorzystać?
Websocket vs SSE
Idea Server Sent Events jest podobna do Websocketów. To co różni oba rozwiązania to sposób komunikacji pomiędzy serwerem a klientem (czyli aplikacją w przeglądarce). W Websocketach mamy do czynienia z komunikacją dwukierunkową - oprócz tego, że serwer wysyła dane do klienta to klient może również wysłać odpowiedź na serwer. SSE działa trochę inaczej - wiadomości może jedynie wysyłać serwer do klienta. Jest to pewne ograniczenie ale w większości przypadków jest to wystarczająca funkcjonalność.
Kiedy wykorzystywać SSE?
Tak jak wspomniałem w poprzednim akapicie wszystko zależy od tego co tworzymy. Jeśli są to różnego rodzaju chaty gdzie potrzebujemy komunikacji dwukierunkowej to oczywistym wyborem będzie Websocket. Jednak jeśli potrzebujemy żeby informować stronę o pojedynczych zdarzeniach np.:
- Koniec importu dużego pliku,
- Koniec przetwarzania danego elementu,
- Zmiana na backendzie np.: zmiana danych i konieczność odświeżenia widoku,
wtedy wykorzystanie SSE będzie lepszym rozwiązaniem.
Jak zaimplementować SSE?
Z racji tego, że będziemy tutaj otrzymywać wiadomości z serwera implementację musimy podzielić na dwie części: klienta i serwera. Zaczniemy od części serwerowej. Aby móc wysyłać takie zdarzenia musimy ustawić odpowiednie nagłówki w odpowiedzi na zapytanie:
- ‘Content-Type’: ‘text/event-stream’,
- ‘Cache-Control’: ‘no-cache’,
- ‘Connection’: ‘keep-alive’,
Mając tak ustawione nagłówki jesteśmy w stanie wysłać wiadomości. Wiadomości mają oczywiście swoją strukturę, której trzeba przestrzegać. Mamy tam dostępne 4 pola:
- data - tutaj musimy wpisywać dane jakie chcemy przekazać do klienta w postaci striga. Jeśli potrzebujemy przekazać jakis obiekt najprościej będzie wykorzystać
JSON.stringify()
- id - id zdarzenia
- event - jeśli wysyłamy kilka różnych zdarzeń możemy im nadać konkretne nazwy tak aby klient się subskrybował tylko na konkretne wiadomości
- retry - liczba określająca czas ponownego połączenia podczas próby wysłania zdarzenia
Wiadomości przesyłamy jako zwykłe zmienne tekstowe np.: "data: 'message'"
. Pojedyncze wiadomości są rozdzielane podwójnym znakiem nowej linii \n\n
. Przykład takiego endpointu w Express.js
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
res.flushHeaders();
let i = 0;
setInterval(()=>{
res.write(`id: ${i}\n`);
res.write(`event: event1\n`);
res.write(`data: Message -- ${Date.now()}`);
res.write(`\n\n`);
i++
}, 5000)
});
app.listen(3000);
To teraz przyszedł czas na obsługę tego po stronie klienta. Ogranicza się to tutaj tylko do wykorzystania API EventSource
w przeglądarce. Podajemy tam podczas tworzenia nowej instancji obiektu adres pod którym oczekujemy otrzymywać zdarzenia
const event = new EventSource('http://localhost:3000/events')
Dalsza obsługa zależy od tego jakie zdarzenia będziemy wysyłać. Tak jak wspomniałem jednym z parametrów zdarzenia może być pole event
. Jeśli go podamy to musimy skorzystać z metody addEventListener
event.addEventListener('event_name', (event)=>{
// obsługa
})
Wszystkie inne zdarzenia są obsługiwane przy pomocy funkcji onmessage
event.onmessage = (event) => {
//obsługa
}
I właściwie tyle. Jak widzicie jest to proste - nie potrzebujemy żadnych dodatkowych bibliotek by otrzymywać aktualizację na żywo z serwera. Jestem ciekaw czy znaliście wcześniej to rozwiązanie? No i czy z niego korzystaliście?