XMLHttpRequest open, send

XMLHttpRequest Methoden, Eigenschaften und Events

Das XMLHttpRequest-Objekt nutzen wir vor allem, um Anfragen an den Server zu senden und dann die Antwort als Daten vom Webserver in die Webseite zu laden, und zwar ohne die Webseite neu zu laden. Die wichtigsten Methoden des XMLHttpRequest-Objekts öffnen die Verbindung zum Server und senden den Request.

XHR Methoden

Eigentlich klingt es doch ganz einfach: Das Script braucht eine Resource (z.B. ein Bild oder einen Datensatz). Dafür sendet das Script einen Request ("Hier hast du eine ID, liefere mir dazu ein Bild und den Datensatz") und die Daten werden in der Response geliefert. Das XML im Namen XMLHttpRequest ist irreführend, denn der XMLHttpRequest kann nicht nur XML-Daten zum Server senden oder vom Server holen, sondern auch beliebige andere Formate, und kann den Fortschritt eines Requests prüfen (Tracking). Der XMLHttpRequest – kurz als XHR bezeichnet – ist die Grundlage für die Bearbeitung von Formulardaten: Anfragen zu einer Anwendung senden und die Antwort abfangen und für den Benutzer aufbereiten.

new XMLHttpRequest ()
Neues XHR-Objekt erzeugen
abort ()
Beendet den aktuellen Request
getAllResponseHeaders ()
Gibt den vollständigen Header (Label und Werte) als String zurück. Die Namen-/Wertpaare sind durch Carriage Return/Linefeed-Zeichen voneinander getrennt.
getResponseHeader ("headerLabel")
Gibt den String-Wert eines einzelnen Header-Labels zurück
open ("method", "URL"[, asyncFlag[, "userName"[, "password"]]])
Legt die Ziel-Url, die Methode und weitere optionale Attribute des anstehenden Requests fest
send (content)
Übermittelt den Request, optional mit einem String oder DOM-Objekt-Daten
setRequestHeader ("label", "value")
Weist dem Header ein Label/Wert-Paar zu, das mit dem Request versendet wird

XMLHttpRequest: Formulardaten versenden und Antwort verarbeiten

Teil 1: Grundlagen und Beispiele: XMLHttpRequest

XMLHttpRequest ist ein fertiges Objekt des Browsers, das einen HTTP-Requests mit JavaScript ausführt, z.B. eine Formulareingabe an eine Anwendung auf dem Server schickt und auf eine Antwort vom Server reagiert.





<form id="request" action="registrar.php" method="POST">
	<input type="text" name="myname" id="myname" placeholder="Name">
	<input type="text" name="myemail" id="myemail" placeholder="Email">
	<button>Submit</button>
</form>

Wenn der Benutzer auf Absenden klickt, werden die Formulardaten per XHR – XMLHttpRequest – an die Anwendung auf dem Server versendet. Formulardaten Listen von Schlüssel-/Wertpaaren. Im name-Attribut liegt der Schlüssel, die Benutzereingaben bilden den Wert.

Am Rand: Wenn hingegen Binärdaten (Dateien, Bilder) versenden werden, wird der HTTP-Request zu multipart/form-data: Bilder nachladen mit XHR.

Beispiel: Daten zum Server senden

Für den Anfang braucht Javascript einen Event Handler, der das Submit des Formulars abfängt und das Absenden der Formulardaten an die Anwendung zunächst verhindert.

document.querySelector('#request').addEventListener('submit', (event) => {
    event.preventDefault();               // verhindert das Absenden an die Anwendung auf dem Server
	
    const xhr = new XMLHttpRequest();     // erzeugt das Request-Objekt
    xhr.open('POST', '/javascript/registrar.php');

    const data = new FormData();
	data.append("myname" , document.querySelector("#myname").value);
	data.append("myemail", document.querySelector("#myemail").value);

    xhr.send(data);                      // Versendet die Daten

    xhr.onload = () => {                 // Wenn die Response der Anwendung kommt
        let response = JSON.parse(xhr.responseText);
    }
});

Der Request xhr.send(data) ist asynchron, das Script wartet nicht auf die Antwort vom Server, die Ausführung des Scripts wird nicht unterbrochen, sondern die folgenden Script-Anweisungen werden durchgeführt.

Die Antwort vom Server erscheint erst zu einem späteren Zeitpunkt – das ist das Asynchrone am XMLHttpRequest.

Die Minimal-Anwendung auf dem Server – registrar.php – wirft die Eingabedaten einfach nur zurück. Das Script ist ebenfalls nur rudimentär und kümmert sich nicht um evt. Fehler.

Datensatz anfordern

Das Anfordern von Daten von einer Anwendung ist ein Klassiker für den XMLHttpRequest. Das Fetch-API hat gegenüber dem einfachen XHR-Request einen Vorteil: Promises.

Promises vermeiden tief verschachtelte Callbacks, wenn ein erster XHR einen weiteren Aufruf erzeugt. Aber auch ein XHR-Request lässt sich asynchron / await mit Promise durchführen.

makeRequest ist eine einfache Hilfsfunktion mit drei Argumenten: der Methode des Requests, der URL der Anwendung und den Daten, die an die Anwendung übergeben werden.

makeRequest
function makeRequest(method, url, data ) {
    return new Promise(function (resolve, reject) {
        let xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                resolve (xhr.response);
            } else {
                reject({
                    status: this.status,
                    statusText: xhr.statusText
                });
            }
        };
        xhr.onerror = function () {
            reject({
                status: this.status,
                statusText: xhr.statusText
            });
        };
        xhr.send(data);
    });
}

Innerhalb des Promise wird xhr.onload aufgerufen. Bei einem Status von 2xx übergibt resolve() die Antwort der Anwendung. Wenn nicht, übergibt reject() die Fehlerinformationen.

Damit der XMLHttpRequest direkt (async / await) ausgeführt wird, muss der Aufruf von makeRequest in eine asynchrone Funktion, die makeRequest mit await aufruft.

async function doAjax(data) {
    let result = await makeRequest("POST", "formdata.php", data);
    document.querySelector(".result").innerHTML = result;
    return (result)
}
document.querySelector("button#submit").addEventListener ("click", function () {
	const data = new FormData();
	data.append("item" , document.querySelector("#item").value);
	data.append("price" , document.querySelector("#price").value);
	data.append("data" , "Wird noch mit dazugepackt");
	document.querySelector(".result").innerHTML += doAjax(data);
});

Die PHP-Anwendung in diesem Beispiel wirft die Eingaben nur einfach zurück.

(PHP-Anwendung anzeigen)

<?php
$item = '';
$price = '';
$data = '';
$tx = '';
$submit = '';
$chk = '';
if (isset($_POST['item'])) {
	$item = $_POST['item'];
}

if (isset($_POST['price'])) {
	$price = $_POST['price'];
}

if (isset($_POST['data'])) {
	$data = $_POST['data'];
}

if (isset($_POST['tx'])) {
	$tx = $_POST['tx'];
}

if (isset($_POST['chk'])) {
	$chk = $_POST['chk'];
}

echo json_encode( array ( 
		'item' => $item,
		'price' => $price,
		'data' => $data,
		'chk' => $chk,
		'tx' => $tx
));

Eigenschaften

onreadystatechange
wird bei jeder Änderung des XHR-Zustands aufgerufen
readyState
zeigt grob den Fortschritt während des Requests an.
  • XMLHttpRequest erzeugt, aber open() noch nicht aufgerufen, liefert 0.
  • Signal beim erfolgreichen Eröffnen des XHR ist 1 (OPENED).
  • Signal nach dem Senden und Empfang des Response Headers ist 2 (HEADERS_RECEIVED).
  • Daten werden geladen (LOADING).
  • Das entscheidende Signal beim Beenden der Transaktion ist 4 (DONE).
responseType
ArrayBuffer, Blob, Document, JavaScript JSON-Objekt oder String
response
Je nach gesetztem responseType: ArrayBuffer, Blob, Document, JavaScript JSON-Objekt oder String
responseText
String mit Daten, die vom Server zurück gegeben werden
responseXML
DOM-kompatibles Document-Objekt mit Daten, die vom Server geliefert werden
status
Ein numerischer Code, der vom Server zurückgegeben wird, so wie 404 für "Not Found" oder 200 für "OK"
statusText
Eine Nachricht als String, die den Statuscode begleitet
timeout
upload
withCredentials

Events des XMLHttpRequest

Der ursprüngliche XHR kannte nur onreadystatechange, das alle Antworten behandelt. Mit der Version 2 des XMLHttpRequest sind Events hingekommen (inzwischen wurden XMLHttpRequest 1 und XMLHttpRequest 2 zusammengeführt).

readystatechange
löst bei jeder Änderung des readyState aus
loadstart
Beim Starten des Requests
Datei-Upload und progress
Während Daten gesendet bzw. geladen werden
error
Wenn der Request fehlschlägt
load
Wenn der Request erfolgreich durchgeführt wurde
timeout
Wenn eine vom Script vorgegebene Zeit abläuft, bevor der Request beantwortet wird
loadend
Wenn der Request durchgeführt wurde – entweder erfolgreich oder mit einem Fehler

XHR open()

request.open(method, url, asnc, user, passwword);

Die beiden erforderlichen Parameter der open()-Methode sind die HTTP-Methode und die URL für die Verbindung.

  • GET ist angebracht, wenn es sich im Wesentlichen um eine einfache Anfrage von Daten handelt,
  • POST wird benutzt, wenn die Länge der ausgesendeten Daten größer als 512 Bytes ist.
  • Die URL kann eine vollständige oder relative URL sein.

async ist ein boolescher Wert und legt festlegt, ob die Transaktion asynchron oder synchron durchgeführt werden soll.

  • Die Voreinstellung ist true für einen asynchronen Request: Die Ausführung des Skripts wird sofort nach dem Absetzen der send()-Methode fortgesetzt – ohne auf die Antwort zu warten.
  • Wird der Wert auf false gesetzt, »steht« das Skript und wartet, bis der Request gesendet und die Antwort vom Server angekommen ist.

async="false" gilt heute als »deprecated«, veraltet, da sie vom Benutzer als verwirrend und belastend empfunden wird.

XHR send()

Im Workflow des XMLHttpRequest muss zuerst das XMLHttpRequest-Objekt durch open() initialisiert werden. Im nächsten Schritt werden die Request Headers mitsetRequestHeader gesetzt und am Ende wird send() aufgerufen. Die Event Listener sollten registriert sein, bevor send() aufgerufen wird.

send() sendet einen Request an den Server (bei GET). send(string) sendet einen Request an den Server (bei POST)

let  xml = "<xml><query><autor>Hergé</autor></query></xml>";
xmlhttp.send(xml);
let xhr = new XMLHttpRequest();
xhr.open( 'post', 'formdata.php', true );

xhr.onload () {
}

xhr.send(data);

oder Übertragung von FormData mit Schlüssel-Wertpaaren an

let form = new FormData();
form.append("member", "Heinz");

let formXHR = new XMLHttpRequest();
formXHR.open("POST", "getfile.php");
formXHR.send(form);

FormData ist nicht auf das Senden von Strings begrenzt, sondern kann auch Files und Blobs versenden.

XMLHttpRequest.responseText

Der Zugriff auf die Daten, die der Server zurückliefert, erfolgt über xhr.responseText, xhr.responseXML oder xhr.response.

responseText
Antwort als Text
responseXML
Objekt wird als vollwertiger XML-Knoten des DOM geparst
response
je nachdem, was als responseType gesetzt wurde: ArrayBuffer, Blob, Document, geparst als vollwertiges JavaScript JSON-Objekt oder einfach als String.

Eine Antwort kann z.B. mit XMLHttpRequest.responseText via onreadystatechange empfangen werden, wenn readyState == DONE.

let xhr = new XMLHttpRequest();
   xhr.open ('GET', "xml-data.xml", true);
   xhr.onreadystatechange = function() {
      if(xhr.readyState == 'DONE') {
         console.log (xhr.responseXML);
      }
   }
   xhr.send ('');

oder als JSON-Objekt beim onload-Event:

let xhr2 = new XMLHttpRequest();
xhr2.open("GET", "autor.json", true);
xhr2.responseType = "json";

xhr2.onload = function (e) {
   let obj = xhr2.response;
} 
xhr2.send();

Die Daten, die vom Server zurück geschickt werden, müssen mit einem Content-Type text/xml geliefert werden. Inhalte, die als text/plain oder text/html gesendet werden, werden von der Instanz des Request-Objekts akzeptiert, sind aber nur als Eigenschaft responseText verfügbar.

getResponseHeader / getAllResponseHeaders

Bei einem HEAD-Request gibt der Server nur den Header einer Resource zurück – gerade genug, um den Content-Type oder das Datum der letzten Änderung (Last-Modified) zu erfahren, ohne das Dokument selbst zu öffnen.

Um einen HEAD-Request auszuführen, wird die open()-Methode mit einem HEAD-Request anstelle von GET oder POST aufgerufen und der Header entweder mit getAllResponseHeaders oder getResponseHeader("Name") um einen individuellen Wert zu lesen. Wenn getAllResponseHeaders aufgerufen wird, enthält die Antwort alle Header-Informationen als Name-/Wertpaar und die einzelnen Paare sind durch CR/LF voneinander getrennt.

let xmlhttp = null;
xmlhttp = new XMLHttpRequest();

xmlhttp.open("HEAD", "jsBeispiele/xml/data.xml", true);
xmlhttp.onreadystatechange=function() {
  if (xmlhttp.readyState==4) {
	 let res = document.createElement('pre');
	 let resText = document.createTextNode(xmlhttp.getAllResponseHeaders());
	 res.appendChild(resText);
	 httpHead.appendChild(res);
  }
}
xmlhttp.send(null);

Um einen einzelnen Wert abzurufen, wird xmlhttp.getResponseHeader('name') anstelle von xmlhttp.getAllResponseHeaders() aufgerufen.

xmlhttp.getAllResponseHeaders('Last-Modified');

Sicherheit: Javascript Sandbox

Wenn das XMLHttpRequest-Objekt innerhalb eines Browsers agiert, adaptiert der XMLHttpRequest die Sicherheitsstrategien einer typischen Javascript-Anwendung – die »Sandbox«-Politik.

  1. In den meisten Browsern kann eine Seite, die Skriptcode enthält, nur über das http- bzw. https-Protokoll geladen werden, so dass es nicht ohne Weiteres möglich ist, die Seiten auf dem lokalen Rechner zu testen (file- Protocol). Mozilla fordert, dass der Zugriff auf das Objekt in UniversalBrowserRead-Sicherheitsprivilegien erfolgt. IE hingegen öffnet ein modales Fenster, um den Benutzer von einem potentiellen Sicherheitsproblem zu benachrichtigen und ein Abbrechen der Funktion anzubieten.
  2. Der XMLHttpRequest unterlag in den frühen Versionen noch der Same Origin Policy (alles muss von derselben Domaine kommen): Die URL, die die Daten liefert, muss dieselbe sein wie die des Skripts. So konnten clientseitige Skripte keine Daten von anderen Seiten abrufen und in eine Seite einbinden.

Heute erlaubt XHR Cross Origin Requests, wenn CORS aktiviert ist und unterstützt wird. CORS (Cross-Origin Resource Sharing) ist eine Ebene über dem HTTP-Protokoll und benutzt zusätzliche HTTP-Header, z.B. um ein Bild von gertrud.de in eine Seite von susanne.de zu laden. Für das Laden von CSS-Dateien, Fonts und Scripts im HTML-Code ist das eine Selbstverständlichkeit. Scripte hingegen dürfen nicht ohne Weiteres Resourcen von anderen Domainen laden, es sei denn, dass CORS aktiviert ist.

let xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
	// XHR Chrome / Firefox / Opera / Safari.
	xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
	// XDomainRequest IE
	xhr = new XDomainRequest();
	xhr.open(method, url);
} else {
	// CORS not supported.
	xhr = null;
}

Aspekte: History und Bookmarks

Schon die HTML-Technik der Frames und iFrames bot einen Ansatz, Teile einer Seite stehen zu lassen und nur relevante Daten auszutauschen. Frames gelten heute als veraltetet, da ihr Einsatz mit einer Reihe von schwerwiegenden Nachteilen verbunden ist. Seiten, die aus HTML-Frames aufgebaut sind, lassen sich nicht bookmarken und setzen die History des Browsers außer Kraft.

Auch beim eim Einsatz des XMLHttpRequest funktioniert die klassische History des Browsers nicht mehr, aber Window History wurde erweitert, um den Anforderungen von XHR und JSON entgegen zu kommen.