- WebSocket ist für Echtzeitkommunikation nützlich, aber nicht immer erforderlich; HTTP-basierte Alternativen können einfacher und stabiler sein
- Bei Transaktionsverarbeitung, Verbindungsmanagement und Serverkomplexität kann WebSocket unnötigen Overhead verursachen
- Mit HTTP Streaming und der Bibliothek
eventkit sind Echtzeit-Synchronisierung und Event-Verarbeitung auch ohne WebSocket möglich
Was ist WebSocket?
- WebSocket ist eine Technologie, die einen persistenten bidirektionalen Kommunikationskanal zwischen Client und Server öffnet
- Die Verbindung wird über HTTP gestartet, danach erfolgt die Kommunikation jedoch über ein separates Protokoll
- Sie wird häufig zur Umsetzung von Echtzeit-Anwendungen eingesetzt und ist nützlich, weil sie beidseitige Kommunikation ermöglicht
WebSocket-Nachrichten sind nicht transaktionsbasiert
- WebSocket garantiert keine direkte Zuordnung zwischen Anfrage und Antwort
- Befehle zur Statusänderung und die zugehörigen Ergebnisnachrichten können vermischt im selben Stream eintreffen
- Wenn zum Beispiel ein Client einen Status ändert und ein Fehler auftritt, ist schwer zu erkennen, zu welchem Befehl der Fehler gehört
- Eine Lösung besteht darin,
requestId einzuschließen, um Befehl und Antwort zu verknüpfen, aber das erhöht Komplexität und Verwaltungsaufwand
- Einfacher ist es, Befehle transaktionsbasiert über HTTP zu senden und WebSocket nur für das Broadcasting von Statusänderungen zu verwenden
- Senden und Empfangen lassen sich trennen: die Sendeseite über HTTP-Anfragen, die Empfangsseite über WebSocket oder ein anderes Streaming-Verfahren
Die Schwierigkeiten beim Verwalten des WebSocket-Verbindungslebenszyklus
- Bei WebSocket müssen Start, Ende, Fehler und Wiederverbindung der Verbindung direkt behandelt werden
- Die grundlegende Verarbeitung im Browser umfasst typischerweise das Behandeln von Ereignissen wie Verbindungsaufbau, Nachrichteneingang, Fehlern und Verbindungsende
- Zusätzliche Logik wie Reconnect-Mechanismen, Nachrichtenpufferung und exponentielles Backoff ist erforderlich
- HTTP ist dagegen einfacher zu implementieren, weil Beginn und Ende pro Anfrage klar definiert sind
- Das komplexe Lebenszyklusmanagement ist nur dann gerechtfertigt, wenn es einen klaren Grund für den Einsatz von WebSocket gibt
Mehr Komplexität im Server-Code
- WebSocket muss HTTP-Upgrade-Anfragen verarbeiten, was zusätzliche Handshake-Logik erfordert
- Spezielle Header wie
Sec-WebSocket-Key müssen validiert und passende Response-Header zurückgegeben werden
- Nach dem Aufbau der WebSocket-Verbindung muss ein dauerhafter Zustand für eingehende und ausgehende Nachrichten verwaltet werden; zudem können Probleme wie die Verarbeitung partieller Frames auftreten
- Debugging und Fehlerbehandlung sind schwieriger als bei einer reinen HTTP-Lösung
- Frameworks abstrahieren zwar einen Teil davon, aber die grundlegende Komplexität bleibt bestehen
Alternative: HTTP Streaming
- HTTP unterstützt Streaming von Haus aus; statt einer kompletten Datei kann ein Datenstrom in Echtzeit übertragen werden
- Die Empfangsseite eines klassischen WebSocket-Setups kann vollständig durch HTTP Streaming ersetzt werden
- Mit asynchronen Generatoren lassen sich Status-Updates als Stream verarbeiten
- Ablauf auf der Serverseite
- Status-Updates werden in der Funktion zur Befehlsverarbeitung durchgeführt
- Verbundene Clients erhalten über Generatoren jedes Mal neue Werte, wenn diese verfügbar sind
- Befehle zur Statusänderung werden per HTTP POST gesendet, der Echtzeit-Stream wird über eine GET-Anfrage abonniert
- Ablauf auf der Client-Seite
- Echtzeitdaten werden über die Fetch API und einen Stream Reader empfangen
- Nach dem Dekodieren des Textes wird die UI aktualisiert
- Mit dieser Struktur lässt sich Echtzeit-Synchronisierung auch ohne WebSocket umsetzen
Bonus: Einführung in die Bibliothek eventkit
eventkit ist eine Bibliothek, mit der sich asynchrone Streams einfach aufbauen und beobachten lassen
- Sie ähnelt RxJS, bietet aber ein verbessertes Nebenwirkungsmanagement und ist generatorbasiert entworfen
- Wenn Status-Updates in den Stream gepusht werden, können Clients diese in Echtzeit empfangen
- Mit
Stream und AsyncObservable lässt sich die Implementierung auf Server- und Client-Seite einfach halten
- Einsatz von eventkit auf der Serverseite
- Statusänderungen werden in einen Stream gepusht, den die Clients abonnieren
- Einsatz von eventkit auf der Client-Seite
- Stream-Daten werden empfangen, dekodiert und anschließend zur Aktualisierung der UI verwendet
- Es gibt außerdem ein offizielles GitHub-Repository und eine Anleitung zu HTTP Streaming
GitHub: https://github.com/hntrl/eventkit
3 Kommentare
Hacker-News-Kommentare
Ich glaube nicht, dass HTTP-Streaming für dieses Muster konzipiert wurde. HTTP-Streaming ist dafür gedacht, große Datenmengen in Teile zu zerlegen. Wenn man Streaming wie einen Pub/Sub-Mechanismus verwendet, könnte man es bereuen. HTTP-Zwischenstationen erwarten dieses Traffic-Muster nicht (NGINX, CloudFlare usw.). Jedes Mal, wenn die WLAN-Verbindung abbricht, wird die Fetch-API wahrscheinlich einen Fehler wegen eines fehlgeschlagenen Requests auslösen
Eine RequestID an den Server zu senden, um einen Request/Response-Zyklus zu erhalten, ist weder seltsam noch übertrieben. In ernsthaften Apps ist eine API wie
send(message).then(res => ...)immer wertvollheaders['authorization']liest, muss man auf einconnectionParams-Objekt zugreifen, das so tut, als wären es Request-HeaderBeim Video-Streaming fordert der Client Chunks per Range an, nicht über eine einzelne HTTP-Verbindung
Es ist besser, SSE statt EventKit zu verwenden
Im POC werde ich traditionelle HTTP-Formularübermittlungen verwenden. Mehr braucht es nicht
Das Problem bei HTTP2 ist, dass Server Push auf ein bestehendes Protokoll aufgesetzt wurde. HTTP ist ein Protokoll zur Ressourcenübertragung und fügt unnötigen Overhead hinzu. Der Hauptzweck von HTTP2 ist, dass der Server Dateien/Ressourcen proaktiv an den Client pusht, um Round-Trip-Latenz zu reduzieren
WebSockets senden nicht als Stream, sondern als Datagramme (Pakete). Die WebSockets-API von JavaScript-Bibliotheken kann keinen Backpressure verarbeiten und nicht alle Fehler behandeln. Wenn man sie wie einen TCP-Stream verwenden will, muss man vorsichtig sein
Ich habe es bereut, WebSockets in Produktion ausgerollt zu haben. Es gab Probleme wie NGINX, das Verbindungen nach 4/8 Stunden beendet, und Browser, die nach dem Ruhezustand keine Wiederverbindung herstellen. Wenn möglich, sollte man WebSockets und langlebige Verbindungen vermeiden
Es gibt eine idealisierte Wahrnehmung von WebSockets. Man neigt dazu, WebSockets für Streaming-/Echtzeit-Anwendungsfälle einzusetzen. WebSockets verlieren die Einfachheit und die Vorteile der HTTP-Werkzeuge. Die Lösung für Streaming-Server-Änderungen sind h2/h3 und SSE. Wenn man pro Client auf maximal 0,5 req/s bündeln kann, braucht man keine WebSockets
Wer sich für HTTP-Streaming interessiert, sollte sich Braid-HTTP ansehen. Es erweitert HTTP elegant um Event-Streaming und bietet ein leistungsfähiges Protokoll zur Synchronisierung von Zuständen