Updates mit vom Server gesendeten Ereignissen streamen

Vom Server gesendete Ereignisse (SSEs) senden automatische Updates von einem Server mit einem HTTP- Sobald die Verbindung hergestellt ist, können Server Daten initiieren. Übertragung.

Mit SSEs können Sie Push-Benachrichtigungen aus Ihrer Web-App senden. SSEs senden Informationen nur in eine Richtung. Daher erhalten Sie keine Updates vom Client.

Das Konzept der SSEs ist Ihnen vielleicht bekannt. Eine Webanwendung „abonniert“ zu einem Stream von Aktualisierungen, die von einem Server generiert werden, und beim Auftreten eines neuen Ereignisses eine Benachrichtigung an den Client gesendet. Aber um die vom Server gesendeten Ereignisse wirklich zu verstehen, müssen wir die Grenzen seiner AJAX-Vorgänger zu verstehen. Dazu zählen:

  • Abfrage: Die Anwendung fragt wiederholt Daten von einem Server ab. Diese Technik wird von der Mehrheit der AJAX-Anwendungen genutzt. Mit dem HTTP-Protokoll werden Daten drehen sich um ein Anfrage- und Antwortformat. Der Client stellt eine Anfrage. und wartet, bis der Server mit Daten antwortet. Wenn keiner verfügbar ist, wird ein leeres -Antwort zurückgegeben. Zusätzliches Polling erhöht den HTTP-Overhead.

  • Long Polling (Hanging GET / COMET): Wenn der Server keine Daten hat verfügbar ist, hält der Server die Anfrage so lange offen, bis neue Daten zur Verfügung gestellt werden. Daher wird diese Technik oft als "Hanging GET" (Hanging GET) bezeichnet. Wann? Informationen verfügbar werden, der Server antwortet, die Verbindung schließt und der Prozess wird wiederholt. Daher antwortet der Server ständig mit neuen Daten. Dazu nutzen sie in der Regel Hacks wie das Anfügen von Daten von Script-Tags auf "unendlich" iFrame.

Bei der Entwicklung der vom Server gesendeten Ereignisse wurde von Anfang an auf Effizienz ausgelegt. Bei der Kommunikation mit SSEs kann ein Server Daten per Push-Funktion ohne eine vorherige Anfrage stellen zu müssen. Mit anderen Worten: können Updates sofort vom Server zum Client gestreamt werden. Kleinunternehmen einen einzigen unidirektionalen Kanal zwischen Server und Client öffnen.

Der Hauptunterschied zwischen vom Server gesendeten Ereignissen und Long Polling-Ereignissen besteht darin, direkt vom Browser verarbeitet und der Nutzer muss nur die Nachrichten abhören.

Vom Server gesendete Ereignisse im Vergleich zu WebSockets

Warum sollten Sie sich für vom Server gesendete Ereignisse gegenüber WebSockets entscheiden? Gute Frage.

WebSockets verfügt über ein umfassendes Protokoll mit bidirektionale Vollduplex-Kommunikation. Ein wechselseitiger Kanal ist besser für Messaging-Apps und alle Anwendungsfälle, bei denen echtzeitnahe Updates in beide Richtungen.

Manchmal benötigen Sie jedoch nur eine einseitige Kommunikation von einem Server. Zum Beispiel, wenn ein Freund seinen Status, Börsenticker, Nachrichten-Feeds oder anderen automatisierten Push-Mechanismen. 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 ein Freund.

SSEs werden über HTTP gesendet. Es gibt kein spezielles Protokoll oder Server, um loszulegen. WebSockets erfordern Vollduplex -Verbindungen und neuen WebSocket-Servern, die das Protokoll verarbeiten.

Darüber hinaus verfügen über vom Server gesendete Ereignisse über eine Vielzahl von Funktionen, die WebSockets fehlen. einschließlich automatischer Wiederherstellung der Verbindung, Ereignis-IDs und der Möglichkeit, bestimmten Ereignissen.

EventSource mit JavaScript erstellen

Erstellen Sie zum Abonnieren eines Ereignisstreams ein EventSource-Objekt und übergeben Sie es URL Ihres Streams:

const source = new EventSource('stream.php');

Richten Sie als Nächstes einen Handler für das message-Ereignis ein. Sie können optional Auf open und error warten:

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 gesendet werden, wird der onmessage-Handler ausgelöst. und neue Daten sind in der e.data-Property verfügbar. Der magische Teil ist Sobald die Verbindung getrennt wird, stellt der Browser automatisch wieder eine Verbindung her. und nach etwa 3 Sekunden die Quelle ändern. Ihre Serverimplementierung kann sogar die Kontrolle dieses Zeitlimits für die Wiederverbindung.

Das war's. Ihr Client kann jetzt Ereignisse aus stream.php verarbeiten.

Format des Ereignisstreams

Für das Senden eines Ereignisstreams von der Quelle wird ein Nur-Text-Antwort, bereitgestellt mit einem text/event-stream-Inhaltstyp, das dem SSE-Format entspricht. In ihrer einfachen Form sollte die Antwort eine data:-Zeile enthalten, gefolgt von Ihrem Nachricht, gefolgt von zwei „\n“ Zeichen, um den Stream zu beenden:

data: My message\n\n

Mehrzeilige Daten

Wenn Ihre Nachricht länger ist, können Sie sie in mehrere data:-Zeilen aufteilen. Zwei oder mehr aufeinanderfolgende Zeilen, die mit data: beginnen, werden als einzelnes Datenelement, d. h., nur ein message-Ereignis wird ausgelöst.

Jede Zeile muss mit einem einzigen "\n" enden. (außer dem letzten, das mit zwei). Das Ergebnis, das an den message-Handler übergeben wird, ist ein einzelner String die mit Zeilenumbruchzeichen verkettet sind. Beispiel:

data: first line\n
data: second line\n\n</pre>

Dies erzeugt „erste Zeile\nzweite Zeile“ in „e.data“. Man könnte dann e.data.split('\n').join(''), um die Nachricht ohne „\n“ zu rekonstruieren Zeichen.

JSON-Daten senden

Wenn Sie mehrere Zeilen verwenden, können Sie JSON-Daten senden, ohne die Syntax zu unterbrechen:

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

Möglicher clientseitiger Code zur Verarbeitung dieses Streams:

source.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data.id, data.msg);
});

Ereignis-ID zuweisen

Sie können eine eindeutige ID mit einem Streamereignis senden, indem Sie eine Zeile einfügen, die mit id::

id: 12345\n
data: GOOG\n
data: 556\n\n

Durch Festlegen einer ID kann der Browser das letzte ausgelöste Ereignis verfolgen. Verbindung zum Server unterbrochen wird, wird ein spezieller HTTP-Header (Last-Event-ID) in der neuen Anfrage festgelegt. So kann der Browser bestimmen, welches Ereignis ausgelöst werden soll. Das message-Ereignis enthält eine e.lastEventId-Eigenschaft.

Zeitüberschreitung bei erneuten Verbindung festlegen

Ungefähr drei Sekunden lang wird vom Browser versucht, die Verbindung zur Quelle wiederherzustellen. nachdem jede Verbindung getrennt wurde. Sie können dieses Zeitlimit ändern, indem Sie einen Zeile, die mit retry: beginnt, gefolgt von der Anzahl der Millisekunden warten, bevor Sie versuchen, die Verbindung wiederherzustellen.

Im folgenden Beispiel wird nach 10 Sekunden versucht, eine Verbindung herzustellen:

retry: 10000\n
data: hello world\n\n

Ereignisnamen angeben

Eine einzelne Ereignisquelle kann verschiedene Arten von Ereignissen generieren, indem sie ein Ereignisnamens. Wenn eine Zeile vorhanden ist, die mit event: beginnt, gefolgt von einem eindeutigen Namen für das Ereignis, wird es diesem Namen zugeordnet. Auf dem Client kann ein Ereignis-Listener eingerichtet werden, um auf dieses bestimmte Ereignis zu warten.

Die folgende Serverausgabe sendet beispielsweise drei Ereignistypen: eine generische „Nachricht“ event, „userlogon“ und „update“ Ereignis:

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 Event-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 grundlegende 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 sehen Sie 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 wieder eine Verbindung zur Ereignisquelle her, wenn die Verbindung geschlossen, aber dieses Verhalten kann entweder vom Client oder Server abgebrochen werden.

Um einen Stream vom Client abzubrechen, rufen Sie Folgendes auf:

source.close();

Antworte mit einer Nicht-text/event-stream-Antwort, um einen Stream vom Server abzubrechen Content-Type oder gibt einen anderen HTTP-Status als 200 OK zurück (z. B. 404 Not Found).

Beide Methoden verhindern, dass der Browser die Verbindung wieder herstellt.

Ein Wort zur Sicherheit

Von EventSource generierte Anfragen unterliegen denselben Ursprungsrichtlinien wie anderen Netzwerk-APIs wie Fetch hinzu. Wenn der SSE-Endpunkt auf Ihrem Server verschiedenen Quellen zugänglich sind, finden Sie hier Cross-Origin Resource Sharing (CORS).