Bei Server-Sent Events (SSEs) werden automatische Updates von einem Server über eine HTTP-Verbindung an einen Client gesendet. Sobald die Verbindung hergestellt ist, können die Server die Datenübertragung initiieren.
Sie können SSEs verwenden, um Push-Benachrichtigungen von Ihrer Webanwendung zu senden. SSEs senden Informationen in eine Richtung, sodass Sie keine Updates vom Client erhalten.
Das Konzept der SSEs ist Ihnen vielleicht schon bekannt. Eine Webanwendung „meldet sich für“ einen Stream von Updates an, der von einem Server generiert wird. Jedes Mal, wenn ein neues Ereignis auftritt, wird eine Benachrichtigung an den Client gesendet. Um serverseitige Ereignisse wirklich zu verstehen, müssen wir jedoch die Einschränkungen ihrer AJAX-Vorgänger kennen. Dazu zählen:
Polling: Die Anwendung fragt wiederholt einen Server nach Daten ab. Diese Technik wird von der Mehrheit der AJAX-Anwendungen verwendet. Beim HTTP-Protokoll dreht sich das Abrufen von Daten um ein Anfrage- und Antwortformat. Der Client stellt eine Anfrage und wartet, bis der Server mit Daten antwortet. Ist keine verfügbar, wird eine leere Antwort zurückgegeben. Zusätzliche Abfragen führen zu einem höheren HTTP-Overhead.
Long Polling (hängender GET-/COMET-Vorgang): Wenn auf dem Server keine Daten verfügbar sind, hält der Server die Anfrage offen, bis neue Daten verfügbar sind. Daher wird dieses Verfahren oft als „hängender GET“ bezeichnet. Sobald Informationen verfügbar sind, antwortet der Server, schließt die Verbindung und der Vorgang wird wiederholt. Der Server antwortet also ständig mit neuen Daten. Für die Einrichtung verwenden Entwickler in der Regel Hacks wie das Anhängen von Script-Tags an einen „unendlichen“ Iframe.
Servergesendete Ereignisse wurden von Grund auf für Effizienz entwickelt. Bei der Kommunikation mit SSEs kann ein Server jederzeit Daten an Ihre App senden, ohne dass eine anfängliche Anfrage erforderlich ist. Mit anderen Worten: Updates können direkt vom Server an den Client gestreamt werden. SSEs öffnen einen einzelnen unidirektionalen Kanal zwischen Server und Client.
Der Hauptunterschied zwischen servergesendeten Ereignissen und Long-Polling besteht darin, dass SSEs direkt vom Browser verarbeitet werden und der Nutzer nur auf Nachrichten warten muss.
Servergesendete Ereignisse im Vergleich zu WebSockets
Warum sollten Sie servergesendete Ereignisse anstelle von WebSockets verwenden? Gute Frage.
WebSockets hat ein umfangreiches Protokoll mit bidirektionaler Vollduplex-Kommunikation. Ein Zwei-Wege-Kanal eignet sich besser für Spiele, Messaging-Apps und alle Anwendungsfälle, bei denen in beiden Richtungen Echtzeit-Updates erforderlich sind.
Manchmal ist jedoch nur eine einseitige Kommunikation von einem Server erforderlich.
Das kann z. B. der Fall sein, wenn ein Freund seinen Status aktualisiert, wenn Aktienkurse angezeigt werden oder wenn Nachrichtenfeeds oder andere automatisierte Push-Mechanismen für Daten verwendet werden. Mit anderen Worten: eine Aktualisierung einer clientseitigen Web SQL-Datenbank oder eines IndexedDB-Objektspeichers.
Wenn Sie Daten an einen Server senden müssen, ist XMLHttpRequest
immer eine gute Wahl.
SSEs werden über HTTP gesendet. Es ist keine spezielle Protokoll- oder Serverimplementierung erforderlich. WebSockets erfordern Vollduplex-Verbindungen und neue WebSocket-Server, um das Protokoll zu verarbeiten.
Außerdem bieten serverseitig gesendete Ereignisse eine Vielzahl von Funktionen, die WebSockets von Natur aus nicht haben, darunter automatische Neuverbindungen, Ereignis-IDs und die Möglichkeit, beliebige Ereignisse zu senden.
EventSource mit JavaScript erstellen
Wenn Sie einen Ereignisstream abonnieren möchten, erstellen Sie ein EventSource
-Objekt und übergeben Sie ihm die URL Ihres Streams:
const source = new EventSource('stream.php');
Richten Sie als Nächstes einen Handler für das Ereignis message
ein. Optional kannst du auch nach open
und error
lauschen:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
Wenn Updates vom Server gepusht werden, wird der onmessage
-Handler ausgelöst und neue Daten sind in der zugehörigen e.data
-Property verfügbar. Das Tolle daran ist, dass der Browser nach etwa drei Sekunden automatisch wieder eine Verbindung zur Quelle herstellt, wenn die Verbindung geschlossen wird. Ihre Serverimplementierung kann sogar diese Zeitüberschreitung für die Wiederverbindung steuern.
Das war's. Ihr Kunde kann jetzt Ereignisse von stream.php
verarbeiten.
Ereignisstream-Format
Zum Senden eines Ereignisstreams von der Quelle muss eine Antwort in Form von Nur-Text erstellt werden, die mit einem text/event-stream
-Content-Typ bereitgestellt wird, der dem SSE-Format entspricht.
In der Grundform sollte die Antwort eine data:
-Zeile, gefolgt von deiner Nachricht und zwei „\n“-Zeichen zum Beenden des Streams enthalten:
data: My message\n\n
Mehrzeilige Daten
Wenn Ihre Nachricht länger ist, können Sie sie mithilfe mehrerer data:
-Zeilen aufteilen.
Zwei oder mehr aufeinanderfolgende Zeilen, die mit data:
beginnen, werden als ein einzelnes Datenelement behandelt. Das bedeutet, dass nur ein message
-Ereignis ausgelöst wird.
Jede Zeile sollte mit einem einzelnen „\n“ enden, mit Ausnahme der letzten, die mit zwei enden sollte. Das an deinen message
-Handler übergebene Ergebnis ist ein einzelner String, der durch Zeilenumbruchzeichen zusammengesetzt ist. Beispiel:
data: first line\n
data: second line\n\n</pre>
Dadurch wird in e.data
„erste Zeile\nzweite Zeile“ ausgegeben. Mit e.data.split('\n').join('')
kann die Nachricht dann ohne die Zeichen „\n“ rekonstruiert werden.
JSON-Daten senden
Wenn Sie mehrere Zeilen verwenden, können Sie JSON senden, ohne die Syntax zu brechen:
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
Und hier ein möglicher clientseitiger Code für die Verarbeitung dieses Streams:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
ID mit einem Ereignis verknüpfen
Sie können eine eindeutige ID mit einem Streamereignis senden, indem Sie eine Zeile mit id:
beginnen:
id: 12345\n
data: GOOG\n
data: 556\n\n
Wenn Sie eine ID festlegen, kann der Browser das letzte ausgelöste Ereignis im Blick behalten. Wenn die Verbindung zum Server getrennt wird, wird mit der neuen Anfrage ein spezieller HTTP-Header (Last-Event-ID
) festgelegt. So kann der Browser ermitteln, welches Ereignis ausgelöst werden soll.
Das Ereignis message
enthält das Attribut e.lastEventId
.
Zeitlimit für die Wiederverbindung steuern
Der Browser versucht etwa drei Sekunden nach jeder geschlossenen Verbindung, wieder eine Verbindung zur Quelle herzustellen. Sie können dieses Zeitlimit ändern, indem Sie eine Zeile mit retry:
beginnen, gefolgt von der Anzahl der Millisekunden, die gewartet werden sollen, bevor versucht wird, eine neue Verbindung herzustellen.
Im folgenden Beispiel wird nach 10 Sekunden versucht, die Verbindung wiederherzustellen:
retry: 10000\n
data: hello world\n\n
Ereignisnamen angeben
Eine einzelne Ereignisquelle kann verschiedene Ereignistypen generieren, indem ein Ereignisname angegeben wird. Wenn eine Zeile mit event:
beginnt und von einem eindeutigen Namen für das Ereignis gefolgt wird, wird dem Ereignis dieser Name zugeordnet.
Auf dem Client kann ein Ereignis-Listener für dieses Ereignis eingerichtet werden.
In der folgenden Serverausgabe werden beispielsweise drei Ereignistypen gesendet: ein generisches „message“-Ereignis, „userlogon“ und „update“:
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
Wenn Ereignis-Listener auf dem Client eingerichtet sind:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
Serverbeispiele
Hier ist eine einfache Serverimplementierung in PHP:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
Hier ist eine ähnliche Implementierung in Node.js mit einem Express-Handler:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
Ereignisstream abbrechen
Normalerweise stellt der Browser automatisch eine Verbindung zur Ereignisquelle her, wenn die Verbindung geschlossen wird. Dieses Verhalten kann jedoch entweder vom Client oder vom Server aufgehoben werden.
Wenn du einen Stream vom Client aus abbrechen möchtest, ruf Folgendes auf:
source.close();
Wenn du einen Stream vom Server abbrechen möchtest, antworte mit einem anderen Wert als text/event-stream
Content-Type
oder gib einen anderen HTTP-Status als 200 OK
zurück (z. B. 404 Not Found
).
Beide Methoden verhindern, dass der Browser die Verbindung wiederherstellt.
Sicherheit
Anfragen, die von EventSource generiert werden, unterliegen den Same-Origin-Richtlinien wie andere Netzwerk-APIs wie fetch. Wenn der SSE-Endpunkt auf deinem Server von verschiedenen Ursprüngen aus zugänglich sein soll, lies den Hilfeartikel zum Aktivieren mit Cross-Origin Resource Sharing (CORS).