© Maisei Raman/Shutterstock.com, olenadesign/Shutterstock.com
Aktive Kommunikation vom Server zum Client

Don’t call us, we’ll call you!


Native Applikationen sind derzeit ein großes Thema. Mit WebSocket haben Entwickler nun ein Werkzeug an der Hand, um bidirektionale Kommunikation über ein TCP-basiertes Netzwerkprotokoll zu nutzen.

Moderne Single Page Applications fühlen sich, wenn sie richtig entwickelt wurden, wie eine native Applikation der jeweiligen Umgebung an. Mit Hilfe von Service Workers und weiteren Browserschnittstellen können sie auf einem System installiert und sogar ohne bestehende Netzwerkverbindung weiterverwendet werden. Das herausragende Merkmal einer solchen Applikation ist, dass sie über längere Zeit ohne Reload auskommt. Für den Benutzer wirkt das, als würde es sich um eine native Applikation handeln. Das Frontend lädt alle Daten, die es anzeigen soll, aktiv vom Server. Bewegen sich mehrere Benutzer parallel auf dem Backend und arbeiten damit, indem sie die Daten sowohl lesen als auch neue Informationen produzieren, müssen die anderen Benutzer hiervon erfahren und entsprechende Aktualisierungen erhalten. Bei HTTP, dem Kommunikationsprotokoll, das im Web mit Abstand am häufigsten verwendet wird, wird die Kommunikation immer vom Client angestoßen. Der Server verarbeitet die eingehende Anfrage und sendet eine Antwort zurück. Dabei hat der Server keine Möglichkeit, den Client seinerseits aktiv mit Informationen zu versorgen. Diese Art der Aktualisierung lässt sich jedoch mit einer Reihe verschiedener Schnittstellen und Protokolle lösen.

Die ersten Ansätze der bidirektionalen Kommunikation

Die Anforderung, dass Client und Server gleichberechtigte Partner in der Kommunikation sein sollen, gibt es schon länger. Eines der ältesten Lösungsszenarien, die auf den damals verfügbaren Webstandards aufbauen, ist das Long Polling. Hierbei handelt es sich jedoch nicht um eine saubere Lösung, sondern vielmehr um einen Hack. Beim Long Polling nutzen Sie als Entwickler die Tatsache aus, dass eine HTTP-Anfrage vom Server eine gewisse Zeitverzögerung haben kann, bevor sie vom Client mit einem Timeout-Fehler geschlossen wird. Während die Verbindung offen ist, kann der Server eine Nachricht an den Client zurücksenden. Falls der Server bis zum Timeout keine Nachricht gesendet hat, baut der Browser erneut eine Verbindung auf und wartet wieder bis zum Timeout. Der Nachteil dieser Implementierung ist, dass der ständige Verbindungsaufbau relativ ressourcenintensiv ist und der Server nicht wirklich aktiv eine Nachricht an den Client senden kann. Aus diesen Gründen wird Long Polling mittlerweile nicht mehr eingesetzt. An seine Stelle sind Alternativen wie WebSockets oder Server-sent Events getreten. Dennoch gelten für alle Implementierungen die gleichen Regeln, und auch die Anforderungen haben sich kaum verändert:

  • Sicherheit: Die Verbindung zwischen Client und Server muss verschlüsselt sein. In der Regel werden über solche Kommunikationsverbindungen auch sensible Daten übertragen, die eine Verschlüsselung analog zu HTTPS erforderlich machen.

  • Personalisierung: Der Server muss einen einzelnen Benutzer ansprechen können. Zu diesem Zweck muss jede Verbindung einem bestimmten Benutzer zugeordnet werden können.

  • Broadcasting: Der Server muss Nachrichten an alle aktiven Verbindungen gleichzeitig senden können. Im Idealfall verfügt der Server über eine Möglichkeit, Verbindungen zu Gruppen zusammenzufassen und sie anzusprechen.

  • Fehlerbehandlung: Der Server muss in der Lage sein, auf Verbindungsfehler reagieren zu können.

  • Clean-up: Wird eine Verbindung vom Client oder Server beendet, muss der Server in der Lage sein, die durch die Verbindung gebundenen Ressourcen auch wieder freizugeben.

Neben diesen Anforderungen gibt es noch einen wesentlichen Unterschied zu traditionellen HTTP-Anfragen. HTTP ist ein Request-basiertes Protokoll. Das bedeutet, dass ein Server nicht zwingend durchgehend laufen muss. Das nutzen Skriptsprachen wie beispielsweise PHP aus und reagieren lediglich auf die eingehenden Anfragen. Bei einer dauerhaften Verbindung reicht diese Charakteristik nicht aus, da der Server nicht nur reagiert, sondern auch aktiv werden kann. Das macht eine Art von permanent laufendem Application-Server erforderlich, wie er in einer sehr leichtgewichtigen Ausprägung beispielsweise mit Node.js implementiert werden kann. Im Gegensatz zu einem RESTful-Interface ist die Kommunikation auch in der Regel nicht zustandslos.

Zunächst werfen wir einen Blick auf die Implementierung, die aktuell am häufigsten für die bidirektionale Kommunikation zwischen Client und Server verwendet wird: die WebSocket-Schnittstelle.

WebSockets – ein zusätzliches Kommunikationsprotokoll

Bei WebSockets handelt es sich um ein eigenständiges Protokoll, das auf TCP aufbaut, sich also auf der gleichen Ebene wie HTTP befindet. WebSockets wurden im Zuge der HTML5-APIs eingeführt und werden mittlerweile von allen gängigen Browsern unterstützt. Zur Demonstration implementieren wir eine Liste von Datensätzen, die vom Server aktualisiert werden kann. Schon an diesem relativ einfachen Beispiel sind die meisten relevanten Aspekte zu erkennen.

WebSockets – die Serverseite

Serverseitig setzen wir für die Implementierung unseres Beispiels auf eine Kombination aus Node.js als Plattform und Express als Web-Application-Framework. Die WebSocket-Implementierung können Sie zwar theoretisch selbst übernehmen, müssten jedoch in diesem Fall die WebSocket-Protokollspezifikation umsetzen, wobei der Nutzen in keinem Verhältnis zum Aufwand steht. Also sollten Sie auf eine der etablierten Bibliotheken wie beispielsweise ws zurückgreifen. Mit dem Kommando yarn init -y erzeugen Sie in einem neuen Verzeichnis die package.json-Datei für das Backend. Mit yarn add express ws installieren Sie anschließend die erforderlichen Abhängigkeiten. In Listing 1 sehen Sie die initiale Version der Serverimplementierung.

Listing 1: Serverimplementierung der WebSockets

const https = require('https'); const fs = require('fs'); const express = require('express'); const WebSocketServer = require('ws').Server; const cert = fs.readFileSync('./cert/localhost.cert'); const key = fs.readFileSync('./cert/localhost.key'); const app = express(); app.get('/list', (req, res) => res.json([ { id: 1, title: 'get up', done: true }, { id: 2, title: 'eat', done: false }, ]) ); const server = https.createServer( { key, cert, }, app ); const wss = new WebSocketServer({ server }); wss.on('connection', ws => { ws.on('message', message => { console.log('received', message); }); setTimeout(() => { const message = { type: 'update', payload: { id: 3, title: 'code', done: false }, }; ws.send(JSON.stringify(message)); }, 5000); }); server.listen(8080);

Wie schon erwähnt, ist die Verschlüsselung der WebSocket-Kommunikation einer der wichtigsten Aspekte bei der Umsetzung. Der Quellcode aus unserem Beispiel sorgt dafür, dass sowohl der Express-Webserver als auch die WebSocket-Verbindung verschlüsselt sind. Zu diesem Zweck benötigen Sie eine Zertifikats- und eine Schlüsseldatei. Diese können Sie beispielsweise mit dem Kommandozeilenwerkzeug OpenSSL erstellen. Beide Dateien lesen Sie mit der readFileSync-Funktion des fs-Moduls von Node.js ein. In diesem Fall stellt die synchrone Operation kein Problem dar, obwohl es...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang