Kommunikations-APIs in HTML5 - Welche wann nutzen?

Achtung: dieser Beitrag ist alt! Es kann gut sein, dass seine Inhalte nicht mehr aktuell sind.

Der @yannickoo fragte mich letzte Woche, wie man am besten zwei Browserfenster miteinander kommunizieren lassen kann und da die Antwort auf diese Frage etwas länger als 140 Zeichen ist, gibt es hiermit einen Blogpost zu dem Thema. Dank HTML5 existiert eine Reihe von Technologien, die es ermöglichen via Browser in Echtzeit Nachrichten zu versenden um zu empfangen – manche mit, manche ohne Server-Komponente. Um da für etwas Durchblick zu sorgen, werden im Folgenden Cross-Document-Messaging, Server-Sent Events, Web Sockets und Shared Workers einmal in aller Kompaktheit verglichen.

Cross-Document-Messaging

Mit Cross-Document-Messaging kann man, wie es der Name nahelegt, Nachrichten zwischen zwei Dokumenten in einem Client hin- und hersenden. Wenn man Informationen zwischen mehreren Browserfenstern einer Webapp oder zwischen Frames austauschen möchte, ist Cross-Document-Messaging das Mittel der Wahl. Die Benutzung eigentlich ganz einfach: mit postMessage() kann man Nachrichten an andere window-Objekte senden und diese dort mit dem message-Event empfangen. Dazu braucht man ein Ausgangs-Dokument …

<!doctype html>
<title>Cross-Document-Messaging</title>
<p>
<button id="neu">Neues Fenster aufmachen</button>
<button id="hallo">Hallo senden</button>

<script>
// Das andere Fenster aufmachen
var otherWindow;
document.getElementById('neu').onclick = function(){
    otherWindow = window.open('dokument2.html');
};

// Nachrichten an das andere Fenster (otherWindow) senden
document.getElementById('hallo').onclick = function(){
    if(typeof otherWindow !== 'undefined'){
        otherWindow.postMessage('Hallo!', 'http://localhost');
    }
};

// Nachrichten von anderen Fenstern empfangen
window.onmessage = function(evt){
    alert(evt.data);
};
</script>

… und ein Partner-Dokument für den Austausch. In diesem Fall ist das dokument2.html, das im ersten Dokument per window.open() geöffnet wurde:

<!doctype html>
<title>Cross-Document-Messaging</title>
<button id="hallo">Hallo senden</button>

<script>
// Nachrichten von anderen Fenstern empfangen
window.onmessage = function(evt){
    alert(evt.data);
};

// Nachrichten an das öffnende Fenster (window.opener) senden
document.getElementById('hallo').onclick = function(){
    if(window.opener){
        window.opener.postMessage('Hallo!', 'http://localhost');
    }
};
</script>

Das einige Problem ist, dass man bei der Programmiererung dieser API wissen muss, an wen man sendet – das Ziel sind immer irgendwelche window-Objekte, die dem Script bekannt sein müssen. Wenn aber, wie im obrigen Codebeispiel, Start- und Endpunkte einer Nachrichtenübermittlung bekannt sind (und sie sich alle innerhalb einer Browser-Instanz befinden), kann Cross-Document-Messaging ganz nützlich sein.

  • Use Case: Nachrichtenübermittlung zwischen mehreren Dokumenten auf dem Client
  • Vorteile: Einfach zu benutzen, gute Browserunterstützung
  • Nachteile: Start- und Endpunkte eines Kommunikationskanals müssen bekannt sein
  • Browserunterstützung: Alle Browser, die neuer als der IE7 sind (s. caniuse.com)

Shared Workers

Web Workers sind Threads für JavaScript. Sie ermöglichen es, komplexe Berechnungen auszuführen, ohne dass das Browser-Interface blockiert. Shared Workers sind eine Sonderform von Web Workers, die einen Hintergrundprozess erzeugen, in den sich mehrere Dokumente einklinken können. Dieser Prozess kann dann natürlich nicht nur für Rechenaufgaben, sondern auch für Kommunikation genutzt werden. Wenn Dokument A eine Nachricht in den Prozess sendet …

<!doctype html>
<title>Shared Worker</title>
<input id="foo" type="button">
<script>

// Shared Worker anlegen (Argumente: Dateiname und Name)
var meinWorker = new SharedWorker('sync.js', 'Sync');

// Nachrichten aus dem Worker empfangen
meinWorker.port.onmessage = function(event){
    alert(event.data);
};

// Button bei Klick Nachricht senden lassen
document.getElementById('foo').onclick = function(){
    meinWorker.port.postMessage('Hallo!');
}

</script>
 … kann diese von dort aus an alle eingeklinkten Dokumente weitergeleitet werden:
// sync.js

// Liste der verbundenen Dokumente
var clients = [];

// Bei eingehender Verbindung den Client in der Liste speichern
self.onconnect = function(event){
    var port = event.ports[0];
    clients.push(port);

    // Eingehende Nachricht alle verbundenen Clients verteilen
    port.onmessage = function(msg){
        for(var i = 0; i < clients.length; i++){
            if(clients[i] && clients[i] !== port){
                clients[i].postMessage(msg.data);
            }
        }
    };

};

Das Ganze funktioniert unabhängig davon, ob sich zwischenzeitlich Dokumente verabschieden oder neu einklinken.

  • Use Case: Nachrichtenübermittlung zwischen mehreren Dokumenten auf dem Client, ggf. damit kombinierte Berechnungsaufgaben
  • Vorteile: Anzahl der involvierten Dokumente kann jederzeit variiert werden
  • Nachteile: Nicht ganz einfach zu programmieren und zu debuggen, mäßige Browserunterstützung
  • Browserunterstützung: Chrome, Safari 5+ (inkl. iOS), Opera 10.6+ (s. caniuse.com)

Server-Sent Events

Mit Server-Sent Events kann ein Webserver von sich aus Daten an den Browser senden – und zwar ohne, dass dieser vorher eine Anfrage gestellt haben müsste. Typische Anwendungfälle wären Chats oder Mailclients, bei denen der Server den Browser über neue Nachrichten informieren möchte. Das große Plus von Server-Sent Events ist, dass sich die Umsetzung einer serverseitigen Eventquelle sehr einfach gestaltet; ein simples PHP-Script, das zeilenzweise Text ausgibt, ist alles was man braucht um für Server-Sent Events gerüstet zu sein:

<?php
    header('content-type: text/event-stream');
    echo "data: Dies ist Nachricht Nummer eins";
    echo "\n\n";
    echo "data: Dies ist Nachricht Nummer zwei";

Empfangen lassen sich die Daten über ein einfaches Event:

var source = new EventSource('updates.php');
source.onmessage = function(evt){
    alert(evt.data);
};

Was dieser Technologie fehlt, ist die Möglichkeit, auf gleichem Wege den Server zu kontaktieren.

  • Use Case: Nachrichtenübermittlung vom Server zum Client
  • Vorteile: Serverseitige Umsetzung trivial
  • Nachteile: Kein Rückkanal
  • Browserunterstützung: So gut wie überall außer im IE (s. caniuse.com)

Web Sockets

Das am meisten gehypte Kommunikationstool auf dem HTML5-Bereich sind Web Sockets. Hierbei handelt es sich um eine Browser-API und ein komplett eigenes Netzwerkprotokoll, über das ein Browser und ein Server in beide Richtungen Daten schieben können. Für extrem interaktive Anwendungen wie z.B. Spiele sind Web Sockets damit das Mittel der Wahl. Problematisch ist nur die Server-Komponente, denn ein Programm, das auf dem Server Verbindungen und Daten annimmt und versendet, muss erst mal programmiert werden. Es gibt zwar schon fertige Module für Java, Python und Node, doch wer in anderen Technologie-Ökosystemen lebt, wird unter Umständen seinen eigenen Server programmieren müssen. Hat man das getan, kann mit über eine einfache Senden-Event-Kombo Daten austauschen:

// Verbindung aufbauen
var socket = new WebSocket('ws://server.de:12010/updates');

// Bei erfolgter Verbindung eine Nachricht senden
socket.onopen = function(){
    socket.send('Hallo!');
}

// Nachrichten empfangen
socket.onmessage = function(evt){
    alert(evt.data);
}

Der Haken an der Sache ist, dass in freier Wildbahn eine unüberschaubare Anzahl an Protokollversion von Web Sockets unterwegs sind und ein Client oder ein Server müssten all diese unterschiedlichen Protokolle sprechen. Da das nicht leistbar ist (neue Protokollversionen erscheinen beinahe im Wochentakt), ist der einzig gangbare Weg die Verwendung einer Abstraktionsschicht, die sich um das Protokoll-Chaos kümmert und gegebenenfalls einen Fallback verwendet, ohne dass man sich als Programmierer um diese Details kümmern muss. Socket.IO ist eine solche Schicht, mit der sich spielend leicht ein (in diesem Fall auf Node.js basierender) Server …

// Auf Port 1337 auf neue Verbindungen warten
var io = require('socket.io').listen(1337);

// Wenn eine Verbindung zustande kommt ...
io.sockets.on('connection', function(socket){

    // ... Nachrichten senden ...
    socket.send({
        hello: 'world'
    });

    // ... und empfangen!
    socket.on('message', function(data){
        console.log(data);
    });
});

… und ein Client zusammenbauen lassen:


<script src="http://localhost:1337/socket.io.js"></script>
<script>

// Verbindung aufbauen
var socket = io.connect('http://localhost:1337');

// Eine Nachricht empfangen ...
socket.on('message', function(data){
    alert(data);

    // ... und beantworten
    socket.send({
        world: 'hello'
    });

});

</script>

Je nachdem was die gerade anstehende Browser-Server-Kombination hergibt, verwendet Socket.IO entweder Web Sockets, Flash Sockets oder herkämmliches Polling, um eine so gute Echtzeit-Kommunikation zu gewährleisten, wie es möglich ist. Als Entwickler kann man daher einfach sein Gehirn abschalten und programmieren als gäbe es gar kein Problem mit Web Sockets.

  • Use Case: Bidirektionale Nachrichtenübermittlung zwischen Server zum Client
  • Vorteile: Bidirektionale Echtzeit-Nachrichtenübermittlung!
  • Nachteile: Serverseitige Umsetzung aufwändig bis unmöglich (es sei denn man verwendet Socket.IO o.Ä.)
  • Browserunterstützung (Web Sockets): Überall außer in IE < 10 und dem Android-Browser (s. caniuse.com)
  • Browserunterstützung (Socket.IO): Überall, selbst IE6.

Fazit

Für die Kommunikation innerhalb einer Browser-Instanz hat man die Wahl zwischen Cross Document Messaging und Shared Workers. Letztere braucht man nur, wenn die Anzahl der verbundenen Dokumente sich jederzeit ändern können soll. Andernfalls hat man mit Cross Document Messaging eine sehr einfach zu benutzende API mit guter Browserunterstützung an der Hand, die für die Kommunikation zwischen Fenstern oder Frames wie gemalt ist.

Wenn ein Server mitspielen soll, hat man die Wahl zwischen Server-Sent Events und Web Sockets. Der Hauptunterschied ist, dass Web Sockets einen Rückkanal zum Server bieten und eine eigens programmierte, nicht ganz triviale Serverkomponente benötigt wird. Da sich dieses Problem gemeinsam mit dem Protokoll-Chaos via Socket.IO (das es nicht nur für Node.js gibt, siehe „In other languages“) bequem aus der Welt schaffen lässt, gibt es eigentlich keinen Grund, heutzutage auf Echtzeit-Features in Webapps zu verzichten.

[Update] Web Storage: In einem Kommentar hat Rodney Rehm eine Kommunikationsmethode via Web Storage vorgestellt. Das Ganze geht zwar ein bisschen in Richtung mißbräuchliche Zweckentfremdung (Web Storage ist eigentlich als Speichertechnologie gedacht), hat aber auch Vorteile. Die Technik funktioniert im IE ab Version 8, ist recht einfach zu benutzen und der Absender einer Nachricht braucht keine Kenntnis der angefunkten Endpunkte.

Kommentare (7)

  1. Rodney Rehm

    3. Januar 2012, 12:12 Uhr

    Du könntest noch das Storage Event mit aufnehmen, das an alle Fenster der selben Domain gefeuert wird, wenn man etwas in localStorage schreibt.

    Ist vielleicht etwas einfacher als der im Verhältnis recht aufwändige SharedWorker Spaß (der auch von gar nicht so vielen Browsern unterstützt wird - Firefox 9 kennt SharedWorker beispielsweise immer noch nicht)

    Ich habe damals™ einen Proof Of Concept (BrowserEvent) gebaut, mit dem ich selbst beim IE6 von einander unabhängige Fenster miteinander sprechen lassen konnte. Den produktiven Einsatz dieser Spielerei kann ich jedoch auf Grund hohen CPU-Verbrauchs (json en/decode) nicht empfehlen.

    Btw. schön dich wieder in Langform zu lesen :)

  2. Yannick

    3. Januar 2012, 12:50 Uhr

    Auf diesen Post habe ich so lange gewartet, danke Peter!

  3. Rodney Rehm

    3. Januar 2012, 13:28 Uhr

    Hier ein Demo jsFiddle der storage-Event Geschichte. Etwas aufgepeppt, damit's auch mit dem IE8 klappt. (nein, keine Chance für IE7). Wer oldIE will, wird den WebSocket Ansatz mit Hilfe von socket.io, oder aber das vermutlich noch unperformantere BrowserEvent einsetzten müssen.

  4. Yuri

    3. Januar 2012, 14:08 Uhr

    Bei aller Liebe zu Layouts die sich an den Browser anpassen, aber die Seite ist so breit, Zeilen laufen so lang, dass es wirklich unangenehm zu lesen ist. Auch einen Code kann man ohne scrollen kaum übersichtlich erfassen.

  5. Matthias

    4. Januar 2012, 11:25 Uhr

    Hab grad mit socket.io mit node.js rumgespielt, es ist einfach unschlagbar! Das gab es alles in Flash schon richtig richtig lange, und es war auch recht komfortabel zu programmieren. Jetzt geht es endlich auch in JS - wieder ein Punkt abgehakt.

    Was richtig schick ist, ist die Moeglichkeit, Custom Events und damit verbundene Daten zu senden. Das macht, wenn es einmal geht, richtig Spass - und eroeffnet jede Menge Moeglichkeiten.

    Grosses Problem ist, dass offensichtlich die API hin und wieder geaendert wird, und alle Samples, die etwas aelter sind, nicht mehr funktionieren. Selbiges gilt aehnlich fuer node.js. So richtig komfortabel dokumentiert ist das alles nicht - aber das sind ja auch 0.x-Versionen.

  6. Christian Hufnagel

    5. April 2013, 18:08 Uhr

    Wofür ist bei postMessage() der zweite Paramter mit dem Host?

  7. Peter Kröner

    6. April 2013, 16:06 Uhr

    Mit dem zweiten Parameter kann man festlegen, welche Dokumente bzw. welche Origins das Event empfangen dürfen. Während man mit '*' an potenziell alle sendet, kann man sich mit 'http://foo.com' versichern, dass kein Dokument, das nicht auf foo.com liegt, an die Nachricht kommen kann.

Die Kommentarfunktion ist seit Juli 2014 deaktiviert, weil sie zu sehr von Suchmaschinenoptimierern für manuellen Spam mißbraucht wurde. Wenn du Anmerkungen oder Fragen zum Artikel hast, schreib mir eine E-Mail oder melde dich via Twitter.

Folgt mir!

Kauft mein Zeug!

Cover der HTML5-DVD
Infos auf html5-dvd.de, kaufen bei Amazon

Cover des HTML5-Buchs
Infos auf html5-buch.de, kaufen bei Amazon

Cover des CSS3-Buchs
Infos auf css3-buch.de, kaufen bei Amazon