HTML - Serverseitige Ereignisse

-

Funktionsweise von Server Sent Events

Server Sent Events (SSE) ist eine Technologie, die es einem Server ermöglicht, Daten in Echtzeit an einen Client-Browser zu senden. Die Architektur von SSE basiert auf einem unidirektionalen Kommunikationsmodell, bei dem der Server Daten an den Client sendet, der Client jedoch keine Daten zurück an den Server sendet.

Bei SSE verbindet sich der Client mit dem Server über die EventSource-API. Der Server hält diese Verbindung offen und sendet Events an den Client, sobald neue Daten verfügbar sind. Der Client empfängt diese Events und verarbeitet sie bei Eingang, um die Webseite zu aktualisieren oder andere Aktionen auszulösen.

Die Kommunikation zwischen Client und Server bei SSE erfolgt über eine Standard-HTTP-Verbindung. Der Server sendet Events als Textstrom, wobei jedes Event durch eine Leerzeile getrennt ist. Der Client verwendet die EventSource-API, um auf diese Events zu hören und sie entsprechend zu verarbeiten.

Beispiel einer EventSource-Verbindung

const eventSource = new EventSource('http://example.com/sse');

eventSource.onmessage = function(event) {
    console.log(event.data);
};

Ein wesentlicher Unterschied zwischen SSE und WebSockets liegt im Kommunikationsmodell. Während SSE unidirektional ist, wobei der Server Daten an den Client sendet, ermöglichen WebSockets eine bidirektionale Kommunikation, bei der sowohl Client als auch Server Daten aneinander senden können.

Beispiel einer WebSocket-Verbindung

const socket = new WebSocket('ws://example.com/ws');

socket.onopen = function(event) {
    socket.send('Hello Server!');
};

socket.onmessage = function(event) {
    console.log(event.data);
};

Ein weiterer Unterschied liegt im Nachrichtenformat. Bei SSE werden Nachrichten als Klartext gesendet, wobei jede Nachricht aus einer oder mehreren Zeilen Textdaten besteht. Im Gegensatz dazu verwenden WebSockets ein binäres Nachrichtenformat, das beim Senden großer Datenmengen effizienter sein kann.

Trotz dieser Unterschiede haben sowohl SSE als auch WebSockets ihren Platz in der modernen Webentwicklung. SSE eignet sich gut für Szenarien, in denen der Server Aktualisierungen in Echtzeit an den Client senden muss, wie bei Nachrichtenfeeds oder Börsentickern. WebSockets hingegen sind ideal für Anwendungen, die eine bidirektionale Kommunikation erfordern, wie Chat-Anwendungen oder Mehrspieler-Spiele.

Einrichten von Server Sent Events

Das Einrichten von Server Sent Events erfordert die Konfiguration des Servers und des Clients. Auf der Serverseite müssen Sie ein Skript erstellen, das Events an den Client sendet. Auf der Clientseite müssen Sie ein EventSource-Objekt erstellen, um diese Events zu empfangen und zu verarbeiten.

Serverseitige Einrichtung

Um den Server für das Senden von Events zu konfigurieren, müssen Sie ein Skript erstellen, das auf Client-Anfragen mit den richtigen Headers und Event-Daten antwortet. Der Server sollte eine Antwort mit einem Content-Type-Header von "text/event-stream" senden, der dem Client mitteilt, dass er einen Strom von Events erwarten soll.

Beispiel: Serverseitiger Code mit Node.js und dem Express-Framework

const express = require('express');
const app = express();

app.get('/sse', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });

    setInterval(() => {
        const eventData = `data: Serverzeit ist ${new Date()}\n\n`;
        res.write(eventData);
    }, 1000);
});

app.listen(3000, () => {
    console.log('Server gestartet auf Port 3000');
});

Der Server sendet jede Sekunde ein neues Event an den Client, das die aktuelle Serverzeit enthält.

Beispiel: Serverseitiger Code mit PHP

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$time = date('r');
echo "data: Die Serverzeit ist: {$time}\n\n";
flush();
?>

Das PHP-Skript sendet ein einzelnes Event an den Client, das die aktuelle Serverzeit enthält.

Clientseitige Einrichtung

Auf der Clientseite müssen Sie ein EventSource-Objekt erstellen und die URL des serverseitigen Skripts angeben, das die Events sendet. Das EventSource-Objekt wird sich automatisch mit dem Server verbinden und beginnen, Events zu empfangen.

Beispiel: Clientseitiger Code mit JavaScript

const eventSource = new EventSource('/sse');

eventSource.onmessage = (event) => {
    console.log(event.data);
};

eventSource.onerror = (error) => {
    console.error('EventSource-Fehler:', error);
};

Der Client erstellt ein EventSource-Objekt und gibt die URL des serverseitigen Skripts ('/sse') an. Der onmessage-Event-Handler wird aufgerufen, wenn ein neues Event empfangen wird, und der onerror-Event-Handler wird aufgerufen, wenn ein Fehler auftritt.

Sie können auch bestimmte Event-Typen behandeln, indem Sie Event-Listener hinzufügen:

Beispiel: Behandlung spezifischer Event-Typen

eventSource.addEventListener('timestamp', (event) => {
    console.log('Timestamp-Event:', event.data);
});

Der Client hört auf Events vom Typ 'timestamp' und protokolliert die Event-Daten in der Konsole.

Mit dem serverseitigen und clientseitigen Code ist Ihre Anwendung nun für die Verwendung von Server Sent Events zur Echtzeitkommunikation eingerichtet.

Senden und Empfangen von Events

Senden von Events vom Server

Um Events vom Server an den Client mithilfe von Server Sent Events (SSE) zu senden, müssen Sie formatierte Event-Nachrichten erstellen und versenden. Events bestehen aus einer oder mehreren Textzeilen, wobei jede Zeile durch einen Doppelpunkt und ein Leerzeichen getrennt ist. Jedes Event endet mit einem doppelten Zeilenumbruch (\n\n).

Die häufigsten Event-Felder sind:

Feld Beschreibung
event Gibt den Event-Typ an. Wird es weggelassen, ist der Standard-Event-Typ "message".
data Enthält die Event-Daten. Sie können mehrere Zeilen Daten senden, indem Sie mehrere data-Felder hinzufügen.
id Weist dem Event eine eindeutige ID zu. Dies hilft bei der Wiederverbindung und Fehlerbehandlung.
retry Legt die Wiederverbindungszeit (in Millisekunden) für den Client fest, falls die Verbindung unterbrochen wird.

Beispiel: Server-seitiges Event-Senden

app.get('/sse', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });

    const sendEvent = (eventType, data) => {
        const eventData = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
        res.write(eventData);
    };

    // Sende ein "timestamp" Event jede Sekunde
    setInterval(() => {
        const timestamp = new Date().toLocaleTimeString();
        sendEvent('timestamp', { time: timestamp });
    }, 1000);

    // Sende ein "message" Event, wenn eine Bedingung erfüllt ist
    if (someCondition) {
        sendEvent('message', { text: 'Hallo vom Server!' });
    }
});

Empfangen von Events auf dem Client

Auf der Client-Seite können Sie Events mithilfe des EventSource-Objekts empfangen. Der onmessage Event-Handler wird aufgerufen, wenn ein "message" Event empfangen wird. Sie können benutzerdefinierte Event-Handler für bestimmte Event-Typen mit addEventListener definieren.

Beispiel: Client-seitiges Event-Empfangen

const eventSource = new EventSource('/sse');

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log('Nachricht empfangen:', data.text);
};

eventSource.addEventListener('timestamp', (event) => {
    const data = JSON.parse(event.data);
    console.log('Zeitstempel empfangen:', data.time);
});

Sie können verschiedene Event-Typen behandeln, indem Sie weitere Event-Listener hinzufügen:

Beispiel: Behandlung mehrerer Event-Typen

eventSource.addEventListener('userConnected', (event) => {
    const data = JSON.parse(event.data);
    console.log(`Benutzer ${data.userId} hat sich verbunden`);
});

eventSource.addEventListener('userDisconnected', (event) => {
    const data = JSON.parse(event.data);
    console.log(`Benutzer ${data.userId} hat sich abgemeldet`);
});

Durch das Senden und Empfangen von Events kann Ihre Anwendung einen Echtzeit-Kommunikationskanal zwischen Server und Client aufbauen. So können Sie den Client mit neuen Daten aktualisieren, sobald diese auf dem Server verfügbar sind.

Fehlerbehandlung und Wiederverbindung

Bei der Verwendung von Server Sent Events (SSE) sollten Sie Fehler behandeln und die Verbindung zum Server wiederherstellen, falls sie verloren geht. Dies trägt zu einer zuverlässigeren und robusteren Anwendung bei.

Die Fehlerbehandlung auf der Clientseite umfasst das Abhören des onerror-Ereignisses am EventSource-Objekt. Dieses Ereignis wird ausgelöst, wenn ein Fehler auftritt, wie etwa ein Netzwerkproblem oder ein Problem mit dem serverseitigen Skript.

Beispiel: Fehler auf der Clientseite behandeln

const eventSource = new EventSource('/sse');

eventSource.onerror = (error) => {
    console.error('EventSource-Fehler:', error);
    // Fehlerbehandlung durchführen, z.B. Aktualisierung der Benutzeroberfläche oder Protokollierung des Fehlers
};

Die Wiederverbindung zum Server nach einem Verbindungsverlust ist ein weiterer wichtiger Aspekt der Fehlerbehandlung. Standardmäßig versucht das EventSource-Objekt, sich mit dem Server zu verbinden, wenn die Verbindung unterbrochen wird. Sie können das Wiederverbindungsverhalten jedoch mit den Eigenschaften withCredentials und reconnectionTime steuern.

Beispiel: Wiederverbindungsverhalten steuern

const eventSource = new EventSource('/sse', {
    withCredentials: true,
    reconnectionTime: 5000
});

Die Eigenschaft withCredentials sendet, wenn sie auf true gesetzt ist, Cookies und Authentifizierungs-Header mit der EventSource-Anfrage, was nützlich sein kann, um eine Sitzung über Wiederverbindungen hinweg aufrechtzuerhalten.

Die Eigenschaft reconnectionTime legt die Zeit (in Millisekunden) fest, die gewartet werden soll, bevor ein erneuter Verbindungsversuch zum Server unternommen wird. Standardmäßig verwendet das EventSource-Objekt eine exponentielle Backoff-Strategie, bei der die Wiederverbindungszeit mit jedem fehlgeschlagenen Versuch zunimmt.

Beste Praktiken für Fehlerbehandlung und Wiederverbindung
1. Clientseitige Fehlerbehandlung implementieren: Hören Sie auf das onerror-Ereignis und behandeln Sie Fehler angemessen, z.B. durch Aktualisierung der Benutzeroberfläche oder Protokollierung des Fehlers.
2. Angemessene Wiederverbindungszeit festlegen: Wählen Sie eine Wiederverbindungszeit, die den Bedarf an Echtzeitaktualisierungen mit dem Potenzial, den Server mit zu vielen Wiederverbindungsversuchen zu überlasten, ausgleicht.
3. Serverseitige Heartbeats verwenden: Implementieren Sie serverseitige Heartbeats (periodische vom Server gesendete Nachrichten), um verlorene Verbindungen schneller zu erkennen und zu behandeln.
4. Serverseitige Fehler behandeln: Stellen Sie sicher, dass Ihr serverseitiges Skript Fehler behandeln und entsprechende Fehlermeldungen an den Client senden kann.
5. Fallback-Mechanismen nutzen: Wenn SSE nicht unterstützt wird oder die Verbindung fehlschlägt, erwägen Sie den Rückgriff auf alternative Methoden wie Long-Polling oder WebSockets.

Browser-Unterstützung und Fallback-Lösungen

Server Sent Events (SSE) werden von den meisten modernen Webbrowsern unterstützt, einschließlich Chrome, Firefox, Safari und Edge. Einige ältere Browser unterstützen SSE möglicherweise nicht nativ, weshalb Sie Fallback-Optionen bereitstellen sollten, um die Kompatibilität zu gewährleisten.

Stand 2023 liegt die globale Browser-Unterstützung für SSE laut Can I Use bei etwa 95%. Das bedeutet, dass die Mehrheit der Nutzer SSE ohne Probleme verwenden kann. Dennoch ist es wichtig, Fallback-Optionen für die restlichen 5% der Nutzer zu berücksichtigen, die möglicherweise ältere oder weniger verbreitete Browser verwenden.

Eine Fallback-Option für Browser, die SSE nicht unterstützen, ist die Verwendung von Long-Polling. Beim Long-Polling sendet der Client eine Anfrage an den Server und wartet auf eine Antwort. Hat der Server keine neuen Daten, hält er die Anfrage offen, bis neue Daten verfügbar sind, und sendet dann die Antwort zurück an den Client. Der Client sendet sofort eine weitere Anfrage und wiederholt den Vorgang. Obwohl nicht so effizient wie SSE, kann Long-Polling eine ähnliche Echtzeit-Funktionalität bieten.

Eine weitere Fallback-Option ist die Verwendung von WebSockets. WebSockets bieten einen bidirektionalen Kommunikationskanal zwischen Client und Server und ermöglichen so die Echtzeit-Datenübertragung in beide Richtungen. Wenn ein Browser SSE nicht unterstützt, können Sie prüfen, ob er WebSockets unterstützt und diese als Fallback verwenden.

Für einen nahtloseren Ansatz können Sie Polyfills oder Bibliotheken verwenden, die SSE-Unterstützung für ältere Browser bieten. Ein Polyfill ist ein Codeabschnitt, der die Funktionalität einer neueren Browser-API in einem älteren Browser nachbildet. Zum Beispiel kann der EventSource-Polyfill von Yaffle verwendet werden, um SSE-Unterstützung zu älteren Browsern hinzuzufügen, die keine native Unterstützung haben.

Es gibt auch Bibliotheken wie Pusher und Socket.IO, die Echtzeit-Funktionalität mit verschiedenen Techniken bieten, einschließlich SSE, WebSockets und Long-Polling. Diese Bibliotheken können die Unterschiede zwischen Browsern abstrahieren und eine einheitliche API für Echtzeit-Kommunikation bereitstellen.

Beispiel: Verwendung eines Polyfills zur Ergänzung von SSE-Unterstützung in älteren Browsern

<!-- EventSource-Polyfill einbinden -->
<script src="https://cdn.jsdelivr.net/npm/event-source-polyfill@1.0.9/src/eventsource.min.js"></script>

<script>
if (!window.EventSource) {
    // Falls der Browser EventSource nicht unterstützt, den Polyfill verwenden
    window.EventSource = window.EventSourcePolyfill;
}

const eventSource = new EventSource('/sse');

eventSource.onmessage = (event) => {
    console.log(event.data);
};
</script>

Durch das Einbinden des EventSource-Polyfills und die Prüfung, ob der Browser EventSource nativ unterstützt, können Sie sicherstellen, dass die SSE-Funktionalität für alle Nutzer verfügbar ist, unabhängig von ihrem Browser.

Zusammenfassend lässt sich sagen, dass SSE zwar von den meisten modernen Browsern unterstützt wird, es aber wichtig ist, Fallback-Optionen und Polyfills für ältere Browser zu berücksichtigen. Durch den Einsatz von Techniken wie Long-Polling, WebSockets oder Bibliotheken, die Browser-Unterschiede abstrahieren, können Sie allen Nutzern eine einheitliche Echtzeit-Erfahrung bieten.

Vor- und Nachteile von SSE

Server Sent Events (SSE) haben im Vergleich zu anderen Echtzeit-Kommunikationstechnologien wie WebSockets und Long-Polling mehrere Vor- und Nachteile.

Vorteile

Ein wesentlicher Vorteil von SSE ist, dass Sie Echtzeit-Updates vom Server erhalten können, ohne wiederholt beim Server anfragen zu müssen. Mit SSE kann der Server Updates an den Client senden, sobald neue Daten verfügbar sind. Das bedeutet, der Client hat immer die aktuellsten Informationen, ohne Anfragen an den Server senden zu müssen.

SSE ist auch leichtgewichtiger als WebSockets. WebSockets nutzen eine dauerhafte, bidirektionale Verbindung zwischen Client und Server, was für Situationen, in denen nur der Server Updates an den Client senden muss, zu viel sein kann. SSE hingegen verwendet eine reguläre HTTP-Verbindung zum Senden von Updates, was weniger Overhead bedeutet und in Bezug auf Serverressourcen effizienter sein kann.

Ein weiterer Vorteil von SSE ist die einfachere Implementierung im Vergleich zu WebSockets. Bei SSE müssen Sie nur ein serverseitiges Skript einrichten, das Events an den Client sendet, und ein clientseitiges Skript, das diese Events mit der EventSource API empfängt. WebSockets erfordern dagegen eine komplexere Einrichtung mit einem Handshake-Prozess und einer Protokoll-Aktualisierung.

Nachteile

Ein Nachteil von SSE ist, dass es nur eine Einweg-Kommunikation vom Server zum Client unterstützt. Das bedeutet, der Client kann keine Nachrichten über SSE zurück an den Server senden. Wenn Sie eine bidirektionale Kommunikation benötigen, müssen Sie WebSockets oder eine andere Technologie verwenden, die dies unterstützt.

Ein weiterer Nachteil von SSE ist die im Vergleich zu anderen Technologien wie Long-Polling eingeschränkte Browser-Unterstützung. Während die meisten modernen Browser SSE unterstützen, bieten einige ältere Browser keine native Unterstützung dafür. Das bedeutet, Sie müssen möglicherweise Fallback-Optionen bereitstellen oder Polyfills verwenden, um sicherzustellen, dass Ihre Anwendung in allen Browsern funktioniert.

Beispiel: HTML code with extra spaces

<p>This    is   a   paragraph   with    extra   spaces.</p>

Wenn ein Browser diesen Code rendert, wird der Text wie folgt angezeigt:

This is a paragraph with extra spaces.

Beispiel: Mismatched tags

<p>This is a paragraph.</div>

In diesem Fall wird das öffnende <p>-Tag mit einem </div>-Tag geschlossen, was falsch ist. Die richtige Art, den Absatz zu schließen, ist:

<p>This is a paragraph.</p>

SSE-Anwendungsfälle und Beispiele

Server-Sent Events (SSE) eignen sich für viele Szenarien in Echtzeit-Webanwendungen. Betrachten wir einige gängige Anwendungsfälle und Beispiele, bei denen SSE für eine bessere Benutzererfahrung eingesetzt werden kann.

Ein häufiger Anwendungsfall für SSE sind Echtzeit-Datenaktualisierungen. Eine Börsenticker-Anwendung könnte SSE nutzen, um Echtzeit-Aktienkursaktualisierungen an den Client zu senden. Sobald sich ein Aktienkurs auf dem Server ändert, kann er ein Ereignis mit dem aktualisierten Preis an den Client senden. Der Client kann dann die Benutzeroberfläche aktualisieren, um den neuen Preis anzuzeigen, ohne dass der Benutzer die Seite neu laden muss.

Beispiel: Echtzeit-Aktienaktualisierungen

const express = require('express');
const app = express();

// Beispielhafte Aktiendaten
let stockData = {
    'AAPL': 150.23,
    'GOOGL': 2321.01,
    'MSFT': 300.45
};

// Sende Aktienaktualisierungen alle 5 Sekunden
setInterval(() => {
    // Aktualisiere Aktienkurse zufällig
    for (const symbol in stockData) {
        stockData[symbol] += Math.random() * 10 - 5;
    }

    // Sende aktualisierte Aktiendaten an Clients
    sendEvent('stockUpdate', stockData);
}, 5000);

// ...
const eventSource = new EventSource('/sse');

eventSource.addEventListener('stockUpdate', (event) => {
    const stockData = JSON.parse(event.data);

    for (const symbol in stockData) {
        const price = stockData[symbol].toFixed(2);
        document.getElementById(symbol).textContent = price;
    }
});

Ein weiterer Anwendungsfall für SSE ist das Senden von Benachrichtigungen und Warnungen an den Client. Eine Social-Media-Anwendung könnte SSE verwenden, um Benutzer zu benachrichtigen, wenn sie eine neue Nachricht erhalten oder wenn jemand sie in einem Beitrag erwähnt. Der Server kann ein Ereignis an den Client senden, sobald eine neue Benachrichtigung verfügbar ist, und der Client kann die Benachrichtigung dem Benutzer anzeigen.

SSE kann auch für Live-Chat-Anwendungen verwendet werden. Wenn ein Benutzer eine Nachricht sendet, kann der Server diese Nachricht mittels SSE an alle verbundenen Clients übertragen. Jeder Client würde auf neue Nachrichtenereignisse hören und die Chat-Benutzeroberfläche entsprechend aktualisieren. Beachten Sie jedoch, dass SSE unidirektional ist, sodass Sie eine andere Methode (wie eine reguläre HTTP-POST-Anfrage) verwenden müssten, um Nachrichten vom Client an den Server zu senden.

SSE kann für Echtzeit-Analysen und -Metriken eingesetzt werden. Eine Server-Überwachungsanwendung könnte SSE verwenden, um Echtzeit-Servermetriken an den Client zu senden. Der Client könnte diese Metriken in einem Dashboard anzeigen und Administratoren so eine Echtzeit-Ansicht der Serverleistung bieten.

Beispiel: Echtzeit-Servermetriken

// Sende Servermetriken jede Sekunde
setInterval(() => {
    const serverMetrics = {
        cpu: os.loadavg()[0],
        memory: os.freemem() / os.totalmem()
    };

    sendEvent('serverMetrics', serverMetrics);
}, 1000);
eventSource.addEventListener('serverMetrics', (event) => {
    const metrics = JSON.parse(event.data);

    document.getElementById('cpuUsage').textContent = `${metrics.cpu.toFixed(2)}%`;
    document.getElementById('memoryUsage').textContent = `${(metrics.memory * 100).toFixed(2)}%`;
});

Dies sind einige Beispiele dafür, wie SSE in Echtzeit-Webanwendungen eingesetzt werden kann. Die unidirektionale Natur von SSE macht es gut geeignet für Szenarien, in denen der Server Aktualisierungen an den Client senden muss, der Client aber keine Daten an den Server zurücksenden muss.