Sehen wir uns nochmal das Aufrufexperiment zu JSONP an, bevor wir uns dem Programmcode zuwenden.

Ausprobieren:

JSONP-Abruf von einem 'fremden' Server:

Antwort:

Der Klick auf den Button veranlasst den Browser (genauer JavaScript), eine JSONP-Anfrage an einen entfernten Server zu senden. Der Server (hier ist das http://webservice.helmutkarger.de/php/jsonp.php) sendet eine Antwort zurück, die dann innerhalb des schwarzen Rahmens angezeigt wird.

JSONP am Client

JavaScript Code:
function jsonp(){
  // Löschen bereits vorhandener JSONP Skripte
  var scripts = document.getElementsByTagName("script");
  for (i=0; i<scripts.length; i++) {
    var url = scripts[i].getAttribute("src");
    if(!url) continue;
    if(url.indexOf("callback")>=0) {
      scripts[i].parentNode.removeChild(scripts[i]);
    }
  }

  // Anlegen und Einfügen des neuen Skripts
  var now = new Date();
  url = "http://webservice.helmutkarger.de/php/jsonp.php?time="
        +now.getTime()+"&callback=callback";
  var script = document.createElement("script");
  script.setAttribute("src", url);
  script.setAttribute("type", "text/javascript");
  document.getElementsByTagName("head")[0].appendChild(script);
}

// Entgegennahme der Serverantwort
function callback(data) { 
  document.getElementById("jsonp_antwort").innerHTML = data;
}

Hinter dem kleinen Experiment steht clientseitig nebenstehender JavaScript Code, der nun detaillierter betrachtet wird:

Die Funktion jsonp() wird aufgerufen, um den JSONP-Transfer auszuführen. Für den Fall, dass wir der Funktion Daten zum Senden übergeben wollten (was in unserem Beispiel nicht der Fall ist), könnte das so aussehen:
function jsonp(daten) {....

Die ersten Zeilen der Funktion dienen lediglich dazu, vorhergehende Scripte aus dem DOM zu entfernen, um den Vorgang mehrfach ausführen zu können. Dazu werden alle vorhandenen Scripte in das Array "scripts" eingelesen. Dann wird das Array durchlaufen und für jedes Skript die URL geprüft, ob sie den String "callback" enthält und es sich damit um ein JSONP-Skript handelt. In diesem Fall wird das Skript gelöscht, alle anderen bleiben unversehrt.

Danach wird ein neues Skript konstruiert, bestehend aus den Attributen "scr" und "url". Die URL ist dabei das Interessante. Sie besteht aus dem serverseitigen Programm, das angesprochen werden soll, hier "http://webservice.helmutkarger.de/php/jsonp.php" und zwei Parametern ("time" und "callback"), die mit Fragezeichen bzw. kaufmännischem Und an die URL angehängt werden.

Der erste Parameter "time" ist ein einfacher Zeitstempel (Millisekunden seit dem 1.1.1970), der sicherstellen soll, dass jeder JSONP-Aufruf mit einer unterschiedlichen URL erfolgt. Würden wir diesen Kniff nicht anwenden, würden einige Browser den Abruf intern cachen (zwischenspeichern) und ein Mehrfachaufruf der Funktion würde nicht funktionieren.

Der zweite Parameter "callback" übergibt dem Server den Namen der Callback-Funktion (die in diesem Fall auch "callback" heißt). Weder der Parameter noch dessen Wert müssen "callback" heißen, Client und Server müssen sich nur einig sein, damit der Server den Parameter richtig auslesen kann. "&rueckmeldung=datenentgegennahme" zum Beispiel wäre genauso gültig.

Die letzte Zeile der funktion "jsonp()" fügt schließlich den zusammengebauten Skriptaufruf per script tag injection ins DOM ein. Damit wird die Anforderung an den Server abgeschickt.

Die zweite Funktion wird aufgerufen, sobald der Server seine Antwort sendet. Diese Antwort wird vom Browser als Skript interpretiert und da dieses vom Server so formuliert wurde, dass es lediglich aus einem Aufruf der Callbackfunktion besteht, bekommt die Funktion "callback" die Antwortdaten übergeben. Hier ist das ein String, der auf der HTML-Seite ausgegeben wird. Der Name dieser Funktion muss natürlich dem Namen entsprechen, der im Aufruf an den Server übergeben wurde.

JSONP am Server

PHP Code:
<?php
# auf Callback-Parameter prüfen
if (isset($_GET["callback"]) && !empty($_GET["callback"])) {
  $callback = $_GET["callback"];
# IP-Adresse ermitteln
  $ip = $_SERVER['REMOTE_ADDR'];
# Rückmeldung zusammenbauen
  $nachricht = "Ihre IP-Adresse lautet:<br />"
               .$ip."<br />"
               ."Abgefragt am:<br /> "
               .date("d.m.Y") . " um " . date("H:i:s")
               ." Uhr (Serverzeit).";
# Header für ein JavaScript
  header("Content-Type: application/javascript");
# Rückmeldung senden
  echo $callback."('".$nachricht."')";
}
?>

Auf der Serverseite muss die Anfrage entgegengenommen und eine Antwort im richtigen Format gesendet werden. Das Beispiel verwendet dazu ein kurzes PHP-Programm. Im Einzelnen funktioniert die Serververarbeitung so:

Zuerst wird geprüft, ob der Parameter "callback" übergeben wird und ob er einen Wert enthält. Falls nein könnte, anstelle still zu sterben, auch eine Fehlermeldung an den Client zurückgesendet werden. Ohne Verweis auf die Callbackfunktion, wird diese jedoch beim aufrufenden JavaScript-Programm nicht ankommen.

Sollten weitere Parameter vom Client gesendet werden (was in unserem Beispiel nicht der Fall ist) können diese an dieser Stelle geprüft werden.

Im nächsten Schritt erfolgt die Verarbeitung des Programms, die sich hier darauf beschränkt, die IP-Adresse des Clients zu ermitteln und eine Nachricht zur Rücksendung zu formulieren. Das Datenformat ist hier ein einfacher String, bei komplexeren Daten würde sich JSON anbieten.

Im HTTP-Header wird als "Content-Type" "application/javascript" vorgegeben. Und in der Tat beruht JSONP ja darauf, die Daten verpackt in einer JavaScript-Funktion zu übertragen. Und genau das passiert in der letzten Zeile: Der übergebene Name der Callback-Funktion wird der Antwort vorangestellt und der Dateninhalt als Parameter dahinter in Klammern gesetzt.

Überlegungen zur Verbesserung der Programme

Exkurs: Was ist eigentlich ...
URL-Encoding?

Wenn Daten in der URL übertragen werden sollen, gilt es zu beachten, dass dort bestimmte Zeichen nicht erlaubt sind, wie zum Beispiel das Leerzeichen oder dass andere Zeichen für bestimmte Zwecke reserviert sind, wie etwa "?" und "&". Diese Zeichen müssen kodiert werden.

Das erledigen Funktionen zum URL-Encodieren und -Decodieren, die in JavaScript und PHP bereits enthalten sind. Ein Fragezeichen wird damit zu "%3F", ein kaufmännisches Und zu "%26" und ein Leerraum zu einem "+"-Zeichen.

Die beiden dargestellten Programme dienen der Veranschaulichung der JSONP-Funktionalität, sie sind voll funktionsfähig, im Praxiseinsatz können aber noch einige Verbesserungen angebracht werden:

In unserem Beispiel werden keine Nutzdaten vom Client an den Server übergeben. Die Programme sind aber leicht dahingehend erweiterbar. Auf JavaScript-Ebene müssen die Nutzdaten in die URL eingebaut werden. Zum Beispiel in Form von "..&data='ABC'..".

Anstelle von "plain/text" sollte bei komplexeren Daten JSON als Datenformat verwendet werden. PHP besitzt dazu bereit eingebaute Funktionen, für JavaScript gibt es json2.js auf www.json.org.

Darüber hinaus sollten die Dateninhalte URL-encoded werden, damit reservierte Zeichen nicht den Aufbau der URL-Zeile zerstören.

Auf die eingeschränkten Möglichkeiten der Fehlerbehandlung bei JSONP wurde bereits hingewiesen. Eine Skript-Datei, die im HTML-Code per "<script>"-Tag abgerufen wird, erzeugt keine Fehlermeldung, falls das Skript nicht abgerufen werden kann. Mit Hilfe eines Timers "setTimeout()" in JavaScript könnte aber zumindest überprüft werden, ob nach einer vorgegebenen Zeit eine Serverantwort eingegangen ist.

Das serverseitige PHP-Skript stellt einen Webservice dar, der universell von allen Clients aus nutzbar ist. (Probieren Sie es ruhig, verwenden Sie obigen JavaScript-Code auf Ihrer eigenen Website und sprechen Sie den Webservice auf http://webservice.helmutkarger.de/php/jsonp.php an.) Bei einem Webservice, den Sie selbst schreiben, ist es aber vielleicht nicht gewünscht, dass er ungefragt von anderen Sites aus genutzt werden kann. In diesem Fall muss ein Authentifizierungsverfahren zwischen Client und Server implementiert werden.

<< JSONP

Beispiel >>