Specifikace Server-Sent Events (Události poslané serverem) popisuje zabudovanou třídu EventSource, která udržuje spojení se serverem a umožňuje z něj přijímat události.
Spojení je trvalé, podobně jako u WebSocket.
Je tady však několik důležitých rozdílů:
WebSocket |
EventSource |
|---|---|
| Obousměrné: klient i server si mohou vyměňovat zprávy | Jednosměrné: data posílá pouze server |
| Binární i textová data | Pouze text |
| Protokol WebSocket | Obvyklý HTTP |
EventSource představuje slabší způsob komunikace se serverem než WebSocket.
Proč by ho tedy někdo měl používat?
Hlavním důvodem je, že je jednodušší. Pro mnoho aplikací je WebSocket příliš silný.
Když potřebujeme přijímat tok dat ze serveru, třeba zprávy z chatu, ceny z burzy nebo cokoli jiného, hodí se k tomu EventSource. Navíc podporuje obnovu spojení, což při použití WebSocket musíme implementovat ručně. Kromě toho je to starý planý HTTP, není to nový protokol.
Příjem zpráv
Abychom začali přijímat zprávy, stačí nám vytvořit new EventSource(url).
Prohlížeč se připojí k url, bude udržovat spojení otevřené a čekat na události.
Server by měl odpovědět statusem 200 a hlavičkou Content-Type: text/event-stream, pak udržovat spojení a zapisovat do něj zprávy ve speciálním formátu, například:
data: Zpráva 1
data: Zpráva 2
data: Zpráva 3
data: dvouřádková
- Text zprávy následuje po
data:, mezera za dvojtečkou není povinná. - Zprávy jsou oddělovány dvěma konci řádků za sebou
\n\n. - Když chceme poslat konec řádku
\n, můžeme okamžitě poslat dalšídata:(viz 3. zpráva v příkladu).
V praxi se složité zprávy obvykle posílají zakódované do JSONu a konce řádků se v nich kódují jako \n, takže víceřádkové zprávy data: nejsou zapotřebí.
Příklad:
data: {"uživatel":"Jan","zpráva":"První řádek\n Druhý řádek"}
…Můžeme tedy předpokládat, že jedna data: obsahují právě jednu zprávu.
Pro každou takovou zprávu se vygeneruje událost message:
let eventSource = new EventSource("/events/subscribe");
eventSource.onmessage = function(událost) {
console.log("Nová zpráva", událost.data);
// pro tok dat uvedený v příkladu bude logovat 3krát
};
// nebo eventSource.addEventListener('message', ...)
Požadavky jiného původu
EventSource podporuje požadavky jiného původu, stejně jako fetch a ostatní metody pro práci se sítí. Můžeme použít jakoukoli URL:
let zdroj = new EventSource("https://another-site.com/events");
Vzdálený server obdrží hlavičku Origin a musí odpovědět hlavičkou Access-Control-Allow-Origin, aby bylo možné pokračovat.
Když chceme předat přihlašovací údaje, měli bychom nastavit volbu withCredentials, například:
let zdroj = new EventSource("https://another-site.com/events", {
withCredentials: true
});
Podrobnosti o hlavičkách jiného původu najdete v kapitole Fetch: požadavky jiného původu.
Obnova spojení
Po vytvoření se new EventSource připojí k serveru. Pokud bude spojení přerušeno, připojí se znovu.
To je velmi praktické, protože se o to nemusíme starat.
Mezi opětovnými připojeními nastává krátká prodleva, standardně několik sekund.
Server může nastavit doporučenou prodlevu řádkem retry: v odpovědi (v milisekundách):
retry: 15000
data: Ahoj, nastavuji prodlevu obnovy spojení na 15 sekund
Řádek retry: může přijít společně s daty nebo jako samostatná zpráva.
Prohlížeč by měl před obnovou spojení počkat uvedený čas v milisekundách. Může počkat i déle, např. pokud ví (od operačního systému), že momentálně není síťové připojení dostupné, může počkat, než se objeví, a pak se zkusit připojit.
- Pokud server chce, aby prohlížeč přestal obnovovat spojení, měl by odpovědět HTTP statusem 204.
- Pokud prohlížeč chce uzavřít spojení, měl by volat
eventSource.close():
let eventSource = new EventSource(...);
eventSource.close();
K obnově připojení navíc nedojde tehdy, pokud odpověď obsahuje nekorektní Content-Type nebo obsahuje jiný HTTP status než 301, 307, 200 nebo 204. V takových případech bude vyvolána událost "error" a prohlížeč se znovu nepřipojí.
Když je spojení definitivně uzavřeno, není možné je nijak „znovuotevřít“. Jestliže se chceme připojit znovu, musíme vytvořit nový EventSource.
Identifikátor zprávy
Když se spojení přeruší kvůli problémům v síti, žádná ze stran nemůže s jistotou vědět, které zprávy byly přijaty a které ne.
Abychom mohli spojení korektně obnovit, měla by každá zpráva obsahovat pole id, například:
data: Zpráva 1
id: 1
data: Zpráva 2
id: 2
data: Zpráva 3
data: dvouřádková
id: 3
Když je přijata zpráva obsahující id:, prohlížeč:
- Nastaví vlastnost
eventSource.lastEventIdna hodnotu tohotoid. - Po opětovném připojení pošle hlavičku
Last-Event-IDs tímtoid, aby server mohl znovu poslat následující zprávy.
id: až za data:Prosíme všimněte si, že server připojuje id až za data zprávy, aby zajistil, že lastEventId bude aktualizováno až po přijetí zprávy.
Stav spojení: readyState
Objekt EventSource obsahuje vlastnost readyState, která nabývá jedné ze tří hodnot:
EventSource.CONNECTING = 0; // probíhá připojení nebo obnova připojení
EventSource.OPEN = 1; // připojeno
EventSource.CLOSED = 2; // spojení uzavřeno
Když je objekt vytvořen nebo se spojení přeruší, stav je vždy EventSource.CONNECTING (roven 0).
Z této vlastnosti můžeme zjistit stav EventSource.
Druhy událostí
Objekt EventSource standardně generuje tři události:
message– zpráva přijata, k dispozici vevent.data.open– spojení otevřeno.error– spojení nemůže být vytvořeno, např. server vrátil HTTP status 500.
Server může specifikovat další druhy událostí na začátku události v řádku event: ....
Příklad:
event: join
data: Bob
data: Ahoj
event: leave
data: Bob
Pro zpracování vlastních událostí musíme použít addEventListener, ne onmessage:
eventSource.addEventListener('join', událost => {
alert(`Připojil se ${událost.data}`);
});
eventSource.addEventListener('message', událost => {
alert(`Řekl: ${událost.data}`);
});
eventSource.addEventListener('leave', událost => {
alert(`Odešel ${událost.data}`);
});
Celý příklad
Následující server pošle zprávy obsahující 1, 2, 3, pak bye a přeruší spojení.
Prohlížeč se pak automaticky znovu připojí.
let http = require('http');
let url = require('url');
let querystring = require('querystring');
let static = require('node-static');
let fileServer = new static.Server('.');
function onDigits(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache'
});
let i = 0;
let timer = setInterval(write, 1000);
write();
function write() {
i++;
if (i == 4) {
res.write('event: bye\ndata: bye-bye\n\n');
clearInterval(timer);
res.end();
return;
}
res.write('data: ' + i + '\n\n');
}
}
function accept(req, res) {
if (req.url == '/digits') {
onDigits(req, res);
return;
}
fileServer.serve(req, res);
}
if (!module.parent) {
http.createServer(accept).listen(8080);
} else {
exports.accept = accept;
}<!DOCTYPE html>
<script>
let eventSource;
function start() { // když je stisknuto tlačítko „Start“
if (!window.EventSource) {
// IE nebo jiný starý prohlížeč
alert("Tento prohlížeč nepodporuje EventSource.");
return;
}
eventSource = new EventSource('digits');
eventSource.onopen = function(e) {
log("Událost: open");
};
eventSource.onerror = function(e) {
log("Událost: error");
if (this.readyState == EventSource.CONNECTING) {
log(`Obnova připojení (readyState=${this.readyState})...`);
} else {
log("Nastala chyba.");
}
};
eventSource.addEventListener('bye', function(e) {
log("Událost: bye, data: " + e.data);
});
eventSource.onmessage = function(e) {
log("Událost: message, data: " + e.data);
};
}
function stop() { // když je stisknuto tlačítko „Stop“
eventSource.close();
log("eventSource.close()");
}
function log(zpráva) {
logElem.innerHTML += zpráva + "<br>";
document.documentElement.scrollTop = 99999999;
}
</script>
<button onclick="start()">Start</button> Začněte stisknutím „Start“.
<div id="logElem" style="margin: 6px 0"></div>
<button onclick="stop()">Stop</button> Ukončete stisknutím „Stop“.Shrnutí
Objekt EventSource automaticky zavede stálé spojení a umožní serveru posílat po něm zprávy.
Nabízí:
- Automatickou obnovu spojení s nastavitelnou prodlevou
retry. - Identifikátory zpráv pro události obnovy spojení. Poslední přijatý identifikátor se po obnově spojení pošle v hlavičce
Last-Event-ID. - Aktuální stav se nachází ve vlastnosti
readyState.
To činí z EventSource životaschopnou alternativu k WebSocket, který je nižší úrovně a tyto zabudované vlastnosti postrádá (ačkoli je můžeme implementovat).
Pro mnoho aplikací z reálného života jsou schopnosti EventSource dostatečné.
Je podporován ve všech moderních prohlížečích (ne v IE).
Jeho syntaxe je:
let zdroj = new EventSource(url, [přihlašovacíÚdaje]);
Druhý argument má jen jednu volbu: { withCredentials: true }, která umožňuje posílat přihlašovací údaje na jiný původ.
Celkově je zabezpečení pro jiný původ stejné jako u fetch a jiných síťových metod.
Vlastnosti objektu EventSource
readyState- Aktuální stav spojení:
EventSource.CONNECTING (=0),EventSource.OPEN (=1)neboEventSource.CLOSED (=2). lastEventId- Poslední přijaté
id. Po obnově spojení je prohlížeč pošle v hlavičceLast-Event-ID.
Metody
close()- Uzavře spojení.
Události
message- Zpráva přijata, data jsou v
událost.data. open- Spojení je zavedeno.
error- Nastává v případě chyby, a to jak při ztrátě spojení (bude obnoveno), tak při kritických chybách. Kontrolou
readyStatemůžeme zjistit, zda dochází k obnově spojení.
Server může nastavit vlastní název události v řádku event:. Takové události bychom měli zpracovávat pomocí addEventListener, ne on<událost>.
Formát odpovědi serveru
Server posílá zprávy oddělené \n\n.
Zpráva může obsahovat následující pole:
data:– tělo zprávy, posloupnost vícedatase interpretuje jako jedna zpráva s částmi oddělenými\n.id:– aktualizujelastEventId, při obnově spojení se posílá vLast-Event-ID.retry:– doporučuje délku prodlevy v milisekundách před pokusem o obnovu spojení. V JavaScriptu ji nelze nijak nastavit.event:– název události, musí být předdata:.
Zpráva může obsahovat jedno nebo více polí v libovolném pořadí, ale id: je zpravidla poslední.
Komentáře
<code>, pro několik řádků je obalte značkou<pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)