XMLHttpRequest

Extrem vereinfachtes Nachladen von Daten mit Ajax alias XMLHttpRequest.

"XMLHttpRequest" ist eine Konstruktor-Funktion auf Ebene des Top-Level-Objekts window. Sie dient der Konstruktion eines High-Level-Interfaces für die Abfrage von Dokumenten von Servern. Stellen Sie sich das Ding als Browser im Browser vor, der lediglich keine Anzeige hat und von Ihnen als Programmierer durch Funktionsaufrufe bedient wird statt durch Klicks eines Benutzers!
var http = new XMLHttpRequest();

Die einfachste Art der Benutzung besteht darin, eine URL anzufordern und das resultierende Dokument weiterzuverwenden, wie zum Beispiel:

Abfrage eines einfachen Strings (Text)

http.open("GET","test.txt",false); http.send();

document.getElementById("textbox1").innerHTML = ""
    +"Status: "+http.status+" -> "+http.statusText+"\r\n"
    +http.getAllResponseHeaders()
    +"\r\n"
    +"responseType: "+http.responseType+"\r\n"
    +"response: "+http.response+"\r\n"
    +"responseText: "+http.responseText+"\r\n"
    +"responseXML: "+http.responseXML+"\r\n"
    ;
	

Resultat:

	

Abfrage eines HTML-Dokuments

http.open("GET","test.html",false); http.send();

document.getElementById("textbox2").innerHTML = ""
    +"Status: "+http.status+" -> "+http.statusText+"\r\n"
    +http.getAllResponseHeaders()
    +"\r\n"
    +"responseType: "+http.responseType+"\r\n"
    +"response: "+http.response+"\r\n"
    +"responseText: "+http.responseText+"\r\n"
    +"responseXML: "+http.responseXML+"\r\n"
    ;
	

Resultat:

	

Abfrage eines XML-Dokuments

http.open("GET","test.xml",false); http.send();

document.getElementById("textbox3").innerHTML = ""
    +"Status: "+http.status+" -> "+http.statusText+"\r\n"
    +http.getAllResponseHeaders()
    +"\r\n"
    +"responseType: "+http.responseType+"\r\n"
    +"response: "+http.response+"\r\n"
    +"responseText: "+http.responseText+"\r\n"
    +"responseXML: "+http.responseXML+"\r\n"
    ;
	

Resultat:

	
Das "XMLDocument" können Sie anschließend weiterverarbeiten, wie Sie es von der Benutzung des DOM gewohnt sind...

vereinfachtes Interface für Alltagsanwendungen

Wenn man Teile einer HTML-Seite austauschen lassen will, wünscht man sich eventuell eine weitere Vereinfachung. Solange man auf eh nur irreführende (wir kommen etwas weiter unten gleich darauf zu sprechen...) Gimmicks verzichtet, ist die Syntax ja schon recht kurz, besteht aber immer noch aus dem Dreiteiler "open", "send" und Zugriff auf responseText.

Noch hübscher könnte es SO gehen...

document.getElementById("textbox4").innerHTML = httpreq("test.txt");

Mit dem Ergebnis:

	
	
...wobei man TROTZ weiter vereinfachter Syntax keineswegs auf erweiterte Funktionen verzichten muß. Sie sind Programmierer und haben daher eine Lizenz zum Erfinden von Funktionen. Zum Beispiel mit asynchronem Laden:

var textbox5 = document.getElementById("textbox5");

var onresponse = function(e)
{
    var req = e.target;

    // Die benannten Konstanten des readyState weichen u.U. vom W3C-Standard ab.
    // Bei Mozilla-Browsern ist das der Fall. Vergleiche:
    //  W3C:        http://www.w3.org/TR/XMLHttpRequest/#the-xmlhttprequest-interface
    //  Mozilla:    https://developer.mozilla.org/en/XMLHttpRequest#Attributes
    // Daher muß dieser readyState mit der weniger leserlichen "4" verglichen werden...
    var responseText = "";
    switch(req.readyState)
    {
        case 0: responseText += "request created"; break;
        case 1: responseText += "request opened"; break;
        case 2: responseText += "request sent"; break;
        case 3: responseText += "response loading"; break;
        case 4: responseText += "response completed"; break;
        default: responseText += "undefined state";
    }
    textbox5.innerHTML = responseText;
}

var onprogress = function(e)
{
    textbox5.innerHTML = "response loading: "+e.loaded+(e.lengthComputable?" of "+e.total:"");
}

var asyncxml = httpreq
(
    "test.php",
    null,
    null,
    null,
    null,
    onresponse,
    onprogress
);
	

Ergebnis:

	
	
Denken Sie bitte daran, daß die asynchrone Betriebsart, ungeachtet der Tatsache, daß die Erfinder des XMLHttpRequest sie als Standard gewählt haben, etwas ist, wofür SIE als Programmierer in der Regel EXTRA Aufwand in Ihrem GUI zu treiben haben! Wenn Sie NICHT aufpassen, werden ruck-zuck mehrere gleichzeitig in Auftrag gegebene Anfragen in Ihrem GUI MÖGLICH. Ein einzelnes XMLHttpRequest-Dingens kommt trotz aller Asynchronität nur mit einer einzigen Anfrage gleichzeitig klar. Sie müssen dann entweder für jede Anfrage ein extra XMLHttpRequest-Dingens anlegen oder Locking-Mechanismen installieren oder einen Cache von XMLHttpRequest-Objekten vorhalten oder oder oder... Außerdem müssen Sie verhindern, daß mehrere sich überlappende Anfragen gleichzeitig dieselben Objekte auf Ihrer HTML-Seite manipulieren (wobei eine Gefahr genau dann ausgeht, wenn die XMLHttpRequest-Objekte mit extra Threads implementiert sind; da DAS aber nirgendwo vorgeschrieben (ergo auch nicht verboten) ist, müssen Sie mit dem Schlimmsten rechnen...). Alles nur für eine pur eingebildete Extravaganz! Weil's ja ach so geil aussieht!

=> Denken Sie nach, bevor Sie blindlings Vorschlägen folgen!

Abfrage eines Bild-Dokuments

Um Bilddaten entgegennehmen zu können, müssen die binär im Originalzustand belassen werden. Ein normal als "image" übertragenes Bild wird aber von seiner Kodierung her verändert: Der XMLHttpRequest läßt seine Finger nicht davon, ungeachtet dessen, daß es sich nicht um Textdaten handelt. Das ist unabhängig davon, ob man die Bilddaten über "response" oder "responseText" abgreift. Reichlich unintuitiv. Aber nichts zu machen: Die Erfinder wollten das halt so.

Man kann aber die Umkodierung für Textdaten abschalten, indem man den Mimetyp "text/plain; charset=x-user-defined" erzwingt. Dafür gibt es ab Version 2 des XMLHttpRequest eine Funktion "overrideMimeType". Gemeinerweise unterstützt der momentan aktuellste Internet Explorer diesen Standard noch nicht.
Man könnte aber auch vorerst, um Internet-Explorer-Kompatibilität zu wahren, Bilddaten, die man über XMLHttpRequest abholen will, vom Server mit jenem Mimetyp "text/plain; charset=x-user-defined" verschicken lassen. Wobei das vom Server sicherlich nicht global für alle Bilddaten geschehen soll, sondern eben gezielt nur für die, die mittels XMLHttpRequest abgeholt werden sollen. Dazu kann ein kleines PHP-Script dienen, welches den Mimetyp-Response-Header setzt und die Bilddaten anhängt. Das haben wir hier mal demonstriert.
Wobei die Wirkung fraglich ist: Der IE kann die Bilddaten deshalb noch immer nicht korrekt darstellen - warum auch immer.

Statt der Krücke über einen String kann man die Daten aber auch als "Blob" entgegennehmen lassen, wodurch alles, was wir bis jetzt mühsam per Hand besorgt hatten, schon von den Browserherstellern erledigt wurde.
Allerdings gehört dort die Kenntnis von speziellen, die "Blobs" weiter verarbeitenden Funktionen dazu. Denn das "Blob" als solches ist von seiner Schnittstelle her autistisch veranlagt: Alles, was man mit ihm machen kann, ist, es in seinesgleichen aufzuspalten. Ohne aber jemals an Daten heranzukommen.
DIESE Weiterverarbeitung findet man - wer hätte anderes erwartet? - natürlich NICHT zusammen mit der Blob-Schnittstelle dokumentiert und noch nicht mal als "ferner liefen" referenziert. Aber eine Internet-Suche hilft rasch aus, um zu funktionierenden Beispielen zu kommen und zu entdecken, daß es intern innerhalb der File-Schnittstelle, mit der gemeinsam es in ein und demselben W3C-Dokument definiert wurde, eine Weiterverarbeitung durch die Funktion createObjectURL erfahren kann. Wobei die Internas dieser Funktion wiederum den Browserherstellern überlassen bleiben und den Anwendungsprogrammierer nicht zu interessieren haben...
Blobs können allerdings (warum auch immer) nur im asynchronen Modus des HMLHttpRequest benutzt werden. Daher fällt ein gewisser Overhead mit Reaktionsfunktionen an. Im einfachsten Fall ohne jede Sonderfallbehandlung etwas in der Art von:
http.onload = function(e) {document.getElementById("img2").src = window.URL.createObjectURL(e.target.response);})
Wobei man DAS natürlich auch wie immer verkapseln kann.

Beispiel mit unkodiertem String:
var imgdata_binary = httpreq("getimg.php?img="+encodeURIComponent("ffox-wp-64a.png"));  // developer.mozilla.org: Using XMLHttpRequest
var imgdata_base64 = "data:image;base64,"+base64.encode(imgdata_binary);                // Wikipedia: Data URI scheme
document.getElementById("img").src = imgdata_base64;                                    // W3C: DOM-Level-2 / Image
	

Das ganze könnte man natürlich nochmal hinter einer Funktion verkapseln, zum Beispiel:

document.getElementById("img").src = httpimgreq("ffox-wp-64a.png");


	
	
Beispiel mit Blob
var imghttp = new XMLHttpRequest();
imghttp.open("GET","ffox-wp-64a.png",true);
imghttp.responseType = "blob";
imghttp.onload = function(e)
{
    var http = e.target;
    var text = ""
        +"Status: "+http.status+" -> "+http.statusText+"\r\n"
        +http.getAllResponseHeaders()
        +"\r\n"
        +"responseType: "+http.responseType+"\r\n"
        +"response: "+http.response+"\r\n"
        ;
        
    if (http.status < 300)
    {
        text += ""
            +"Blob.size: "+http.response.size+"\r\n"
            +"Blob.type: "+http.response.type+"\r\n"
            ;
        document.getElementById("img2").src = window.URL.createObjectURL(http.response);
    }
    document.getElementById("textbox62").innerHTML = text;
};
// im einfachsten Fall ohne jede Sonderfallbehandlung:
//imghttp.onload = function(e) {document.getElementById("img2").src = window.URL.createObjectURL(e.target.response);}
imghttp.send();
	


	
	
Will man das verkapseln, muß man die letztendliche Zuweisung der Bilddaten zum DOM-Element in die Verpackung hineinverschieben - wegen der erzwungenen Asynchronität. Zum Beispiel so:
var imgblobreq = function(url,imgid)
{
    var http = new XMLHttpRequest();
    http.open("GET",url,true);
    http.responseType = "blob";
    http.imgid = imgid;
    http.onload = function(e) {document.getElementById(e.target.imgid).src = window.URL.createObjectURL(e.target.response);}
    http.send();
}

imgblobreq("ffox-wp-64a.png","img3");
	



Die vereinfachte Funktion

In den letzten beiden Beispielen wurde die folgende Funktion verwendet, die die Benutzung des XMLHttpRequest-Objekts nochmal vereinfacht...

Nochmal zur Erinnerung und Warnung: Falls Sie tatsächlich eine asynchrone Funktion in Benutzung nehmen (hier: mit onload), MÜSSEN Sie sich ZWINGEND in Ihrer Anwendung auf die Asynchronität einstellen! Weil Anfänger eher Probleme haben, das Ganze überhaupt zu verstehen, rate ich von asynchroner Arbeitsweise zu Anfang AB - auch wenn die Erfinder des XMLHttpRequests sich das anders gedacht hatten. Sie legen sich selbst die Steine, über die Sie stolpern!

var http = new XMLHttpRequest();

var httpreq = function(url,mimetype,method,headers,onload)
{
    if (!method) method = "GET";
    var async = !!onload;
    var thisxml = async ? new XMLHttpRequest() : http;

    thisxml.open(method,url,async);
    
    if (mimetype) thisxml.overrideMimeType(mimetype); // funktioniert nicht beim IE 8

    if (headers) for(var header in headers) thisxml.setRequestHeader(header,headers[header]);
    if (onload) thisxml.onreadystatechange = onload;

    thisxml.send();
        
    if (async) return thisxml;
    return thisxml.responseText;
}

var httpimgreq = function(url,method,headers)
{
    var imgdata_binary = httpreq("getimg.php?img="+encodeURIComponent(url),false,method,headers);
    return "data:image/png;base64,"+base64.encode(imgdata_binary);	// funktioniert AUCH nicht beim IE 8
}
	

Test von zulässigen Parameterwerten

Hmmm... Wozu hatte ich dieses kleine Experiment hier unten angehängt?
Eventuell, um den Lehrgangsteilnehmern klarzumachen, daß man Parameter unter Umständen escapen muß, um bestimmte Zeichen rüberzubringen. Eventuell auch, um den Lehrgangsteilnehmern zu einer Vorstellung von möglichem Hacking zu verhelfen: Sie sollen begreifen lernen, daß ein Hacker jede Möglichkeit des Absetzens von Benutzereingaben rigoros mißbrauchen kann. Und daß die Ziele solcher Angriffe weniger der Server selbst als blauäugige Besucher sind. Und daß man seine Besucher dagegen unter anderem mit Escaping, aber auch mit anderen Techniken schützen kann und sollte...

Dieses einfache Formular hier reicht aus, um jede Art von Schweinerei anzustellen, zum Beispiel ein Overlay über die gesamte Seite legen lassen, in dem man einem Opfer vorgaukelt, es sei mit meinem Server verbunden, aber auf einer ganz anderen Seite, auf der ein Login verlangt wird, wo man zum Beispiel seine Login-Daten vom Lehrgang eintragen sollen täte. Um mal ein Beispiel kreischend an den Haaren herbeizuziehen. Hinreichend dusslige Besucher vorausgesetzt, funktioniert sowas immer: Die "Obrigkeitshörigkeit" geht soweit, daß selbst ein dahergelaufener Haustürverkäufer ins Allerheiligste gelassen wird, da sind ein paar Login-Credentials doch aus dem Handgelenk geschüttelte Peanuts!

Hier werden Varianten demonstriert, wie man User-Input gegen Mißbrauch schützen kann: Typecasting für die Zahlenwerte und Einschränkung auf eine Liste erlaubter Werte für den Operator...

Client
<input type="text" name="a" id="a" value="1"/>
<input type="text" name="o" id="o" value="+" size="2"/>
<input type="text" name="b" id="b" value="2"/> =
<input type="text" name="c" id="c" value="3"/>
<button type="submit" name="submit" onclick="berechnen(); return false;">rechnen!</button>

<script>
var a = document.getElementById("a");
var b = document.getElementById("b");
var c = document.getElementById("c");
var o = document.getElementById("o");

var berechnen = function()
{
    c.value = httpreq("rechner.php?a="+encodeURIComponent(a.value)+"&b="+encodeURIComponent(b.value)+"&o="+encodeURIComponent(o.value));
}
</script>

Server
<?php
require_once $phplib.'stringfuncs.php';
_use('a',$_REQUEST,1); $a = (int) $a;
_use('b',$_REQUEST,2); $b = (int) $b;
_use('o',$_REQUEST,'+');

$erlaubte_ops = '+,-,*,/,&,|,^,&&,||,!';

if (strpos($erlaubte_ops,$o) === false)
{
    echo 'unerlaubt: '.htmlspecialchars($o);
    return;
}

eval('echo '.$a.$o.$b.';');
?>
	

=

Weiterführende Anregungen

Auf der Seite http://www.html5rocks.com/en/tutorials/file/xhr2/ finden sich eine Reihe weiterer Anregungen, was man über die API Level 2 des XMLHttpRequest alles abwickeln kann. Es lohnt sich, bei Bedarf einen Blick hineinzuwerfen! Diese Seite ist natürlich einer der ersten Treffer einer Google-Suche nach "XMLHttpRequest +examples +image". Denken Sie daran, daß Suchmaschinen auch von Ihnen benutzbar sind!