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.