Einführung
Einer der unerkannten Helden im HTML5-Universum ist XMLHttpRequest
.
Streng genommen ist XHR2 keine HTML5-Technologie. Es ist jedoch Teil der kontinuierlichen Verbesserungen, die Browseranbieter an der Hauptplattform vornehmen. Ich füge XHR2 in unsere neue Sammlung hinzu, da es in den heutigen komplexen Webanwendungen eine so wichtige Rolle spielt.
Es hat sich herausgestellt, dass unser alter Freund ein komplettes Makeover bekommen hat, aber viele Nutzer wissen nicht, welche neuen Funktionen es gibt. XMLHttpRequest Level 2 bietet eine Reihe neuer Funktionen, die komplizierte Hacks in unseren Webanwendungen verhindern. Dazu gehören u. a. plattformübergreifende Anfragen, Upload-Fortschrittsereignisse und die Unterstützung für das Hoch- und Herunterladen von Binärdaten. So kann AJAX mit vielen der neuesten HTML5-APIs wie der File System API, der Web Audio API und WebGL zusammenarbeiten.
In dieser Anleitung werden einige der neuen Funktionen in XMLHttpRequest
vorgestellt, insbesondere diejenigen, die für die Arbeit mit Dateien verwendet werden können.
Daten abrufen
Das Abrufen einer Datei als binärer Blob war mit XHR sehr mühsam. Technisch war das gar nicht möglich. Ein gut dokumentierter Trick besteht darin, den MIME-Typ wie unten gezeigt durch einen benutzerdefinierten Zeichensatz zu überschreiben.
Bisherige Methode zum Abrufen eines Bilds:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
// Hack to pass bytes through unprocessed.
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(e) {
if (this.readyState == 4 && this.status == 200) {
var binStr = this.responseText;
for (var i = 0, len = binStr.length; i < len; ++i) {
var c = binStr.charCodeAt(i);
//String.fromCharCode(c & 0xff);
var byte = c & 0xff; // byte at offset i
}
}
};
xhr.send();
Das funktioniert zwar, aber in responseText
wird kein binärer Blob zurückgegeben. Es ist ein Binärstring, der die Bilddatei darstellt.
Wir täuschen dem Server vor, dass die Daten unverarbeitet zurückgegeben werden sollen.
Auch wenn dieser kleine Schatz funktioniert, würde ich ihn als schwarze Magie bezeichnen und von ihm abraten. Jedes Mal, wenn Sie zu Zeichencode-Hacks und Stringmanipulation greifen, um Daten in ein gewünschtes Format zu zwingen, ist das ein Problem.
Antwortformat angeben
Im vorherigen Beispiel haben wir das Bild als binäre „Datei“ heruntergeladen, indem wir den MIME-Typ des Servers überschrieben und den Antworttext als Binärstring verarbeitet haben.
Stattdessen können wir die neuen Properties responseType
und response
von XMLHttpRequest
verwenden, um dem Browser mitzuteilen, in welchem Format die Daten zurückgegeben werden sollen.
- xhr.responseType
- Legen Sie vor dem Senden einer Anfrage den Wert für
xhr.responseType
auf „text“, „arraybuffer“, „blob“ oder „document“ fest, je nach Ihren Datenanforderungen. Hinweis: Wenn Siexhr.responseType = ''
festlegen (oder weglassen), wird die Antwort standardmäßig auf „text“ gesetzt. - xhr.response
- Nach einer erfolgreichen Anfrage enthält die Antworteigenschaft von xhr die angeforderten Daten als
DOMString
,ArrayBuffer
,Blob
oderDocument
(je nachdem, was fürresponseType
festgelegt wurde).
Mit dieser neuen Funktion können wir das vorherige Beispiel überarbeiten, aber dieses Mal das Bild als Blob
statt als String abrufen:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
// Note: .response instead of .responseText
var blob = new Blob([this.response], {type: 'image/png'});
...
}
};
xhr.send();
Viel schöner!
ArrayBuffer-Antworten
Ein ArrayBuffer
ist ein generischer Container mit fester Länge für binäre Daten. Sie sind sehr praktisch, wenn Sie einen allgemeinen Puffer mit Rohdaten benötigen. Der Vorteil dieser Objekte besteht jedoch darin, dass Sie mit JavaScript-Typarrays „Ansichten“ der zugrunde liegenden Daten erstellen können.
Aus einer einzigen ArrayBuffer
-Quelle können sogar mehrere Ansichten erstellt werden.
Sie können beispielsweise ein 8‑Bit-Ganzzahl-Array erstellen, das dieselbe ArrayBuffer
wie ein vorhandenes 32‑Bit-Ganzzahl-Array aus denselben Daten hat. Die zugrunde liegenden Daten bleiben gleich, wir erstellen nur verschiedene Darstellungen davon.
Im folgenden Beispiel wird dasselbe Bild als ArrayBuffer
abgerufen, aber dieses Mal wird aus diesem Datenpuffer ein Array mit ungesignierten 8‑Bit-Ganzzahlen erstellt:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer
// var byte3 = uInt8Array[4]; // byte at offset 4
...
};
xhr.send();
Blob-Antworten
Wenn Sie direkt mit einer Blob
arbeiten und/oder keine der Bytes der Datei bearbeiten möchten, verwenden Sie xhr.responseType='blob'
:
window.URL = window.URL || window.webkitURL; // Take care of vendor prefixes.
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
var img = document.createElement('img');
img.onload = function(e) {
window.URL.revokeObjectURL(img.src); // Clean up after yourself.
};
img.src = window.URL.createObjectURL(blob);
document.body.appendChild(img);
...
}
};
xhr.send();
Ein Blob
kann an verschiedenen Stellen verwendet werden, z. B. zum Speichern in der indexedDB, im HTML5-Dateisystem oder zum Erstellen einer Blob-URL, wie in diesem Beispiel gezeigt.
Daten senden
Es ist zwar toll, Daten in verschiedenen Formaten herunterladen zu können, aber das bringt uns nicht weiter, wenn wir diese umfangreichen Formate nicht an die Basis (den Server) zurücksenden können.
XMLHttpRequest
hat uns vor einiger Zeit dazu veranlasst, nur noch DOMString
- oder Document
-Daten (XML) zu senden. Zum Glück nicht. Eine überarbeitete send()
-Methode wurde überschrieben, um alle folgenden Typen zu akzeptieren: DOMString
, Document
, FormData
, Blob
, File
und ArrayBuffer
. In den Beispielen im restlichen Teil dieses Abschnitts wird gezeigt, wie Daten mit den einzelnen Typen gesendet werden.
Stringdaten senden: xhr.send(DOMString)
function sendText(txt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.responseText);
}
};
xhr.send(txt);
}
sendText('test string');
function sendTextNew(txt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.responseType = 'text';
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.response);
}
};
xhr.send(txt);
}
sendTextNew('test string');
Hier gibt es nichts Neues, das rechte Snippet unterscheidet sich jedoch geringfügig.
Hier wird responseType='text'
für den Vergleich festgelegt. Auch hier führt das Weglassen dieser Zeile zu denselben Ergebnissen.
Formulare senden: xhr.send(FormData)
Viele Nutzer sind wahrscheinlich daran gewöhnt, jQuery-Plug-ins oder andere Bibliotheken für die Verarbeitung von AJAX-Formularübermittlungen zu verwenden. Stattdessen können wir FormData
verwenden, einen weiteren neuen Datentyp, der für XHR2 entwickelt wurde. FormData
eignet sich zum Erstellen einer HTML-<form>
in JavaScript.
Dieses Formular kann dann mit AJAX gesendet werden:
function sendForm() {
var formData = new FormData();
formData.append('username', 'johndoe');
formData.append('id', 123456);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(formData);
}
Im Grunde erstellen wir einfach dynamisch eine <form>
und fügen ihr <input>
-Werte hinzu, indem wir die Methode „append“ aufrufen.
Natürlich müssen Sie eine <form>
nicht von Grund auf neu erstellen.
FormData
-Objekte können von einem vorhandenen HTMLFormElement
auf der Seite initialisiert werden. Beispiel:
<form id="myform" name="myform" action="/server">
<input type="text" name="username" value="johndoe">
<input type="number" name="id" value="123456">
<input type="submit" onclick="return sendForm(this.form);">
</form>
function sendForm(form) {
var formData = new FormData(form);
formData.append('secret_token', '1234567890'); // Append extra data before send.
var xhr = new XMLHttpRequest();
xhr.open('POST', form.action, true);
xhr.onload = function(e) { ... };
xhr.send(formData);
return false; // Prevent page from submitting.
}
Ein HTML-Formular kann Dateiuploads (z.B. <input type="file">
) enthalten und FormData
kann auch damit umgehen. Hängen Sie einfach die Dateien an. Der Browser erstellt dann eine multipart/form-data
-Anfrage, wenn send()
aufgerufen wird:
function uploadFiles(url, files) {
var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) {
formData.append(file.name, file);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
uploadFiles('/server', this.files);
}, false);
Datei oder Blob hochladen: xhr.send(Blob)
Wir können File
- oder Blob
-Daten auch mit XHR senden.
Denken Sie daran, dass alle File
s Blob
s sind. Beides funktioniert also hier.
In diesem Beispiel wird mit dem Blob()
-Konstruktor eine neue Textdatei erstellt und auf den Server hochgeladen.Blob
Außerdem wird ein Handler eingerichtet, um den Nutzer über den Fortschritt des Uploads zu informieren:
<progress min="0" max="100" value="0">0% complete</progress>
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(blobOrFile);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
Ein Byte-Chunk hochladen: xhr.send(ArrayBuffer)
Und zu guter Letzt können wir ArrayBuffer
s als Nutzlast der XHR senden.
function sendArrayBuffer() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
var uInt8Array = new Uint8Array([1, 2, 3]);
xhr.send(uInt8Array.buffer);
}
Cross-Origin Resource Sharing (CORS)
Mit CORS können Webanwendungen in einer Domain domainübergreifende AJAX-Anfragen an eine andere Domain senden. Die Aktivierung ist denkbar einfach. Es muss nur ein einziger Antwortheader vom Server gesendet werden.
CORS-Anfragen aktivieren
Angenommen, Ihre Anwendung befindet sich auf example.com
und Sie möchten Daten von www.example2.com
abrufen. Normalerweise würde bei einem solchen AJAX-Aufruf die Anfrage fehlschlagen und der Browser würde einen Fehler wegen nicht übereinstimmender Herkunftsadresse zurückgeben. Mit CORS kann www.example2.com
Anfragen von example.com
zulassen, indem einfach ein Header hinzugefügt wird:
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Origin
kann einer einzelnen Ressource auf einer Website oder für die gesamte Domain hinzugefügt werden. Wenn alle Domains Anfragen an Sie senden dürfen, legen Sie Folgendes fest:
Access-Control-Allow-Origin: *
Tatsächlich ist CORS auf allen Seiten dieser Website (html5rocks.com) aktiviert. Öffnen Sie die Entwicklertools und Sie sehen die Access-Control-Allow-Origin
in unserer Antwort:
Die Aktivierung von Anfragen zwischen Ursprungen ist ganz einfach. Bitte aktivieren Sie CORS, wenn Ihre Daten öffentlich sind.
Domainübergreifende Anfrage
Wenn CORS für den Serverendpunkt aktiviert ist, unterscheidet sich die Cross-Origin-Anfrage nicht von einer normalen XMLHttpRequest
-Anfrage. Hier ist beispielsweise eine Anfrage, die example.com
jetzt an www.example2.com
senden kann:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
var data = JSON.parse(this.response);
...
}
xhr.send();
Praxisbeispiele
Dateien herunterladen und im HTML5-Dateisystem speichern
Angenommen, Sie haben eine Bildergalerie und möchten eine Reihe von Bildern abrufen und dann mit dem HTML5-Dateisystem lokal speichern.
Eine Möglichkeit dazu besteht darin, Bilder als Blob
anzufordern und mit FileWriter
zu speichern:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
function onError(e) {
console.log('Error', e);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
fs.root.getFile('image.png', {create: true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) { ... };
writer.onerror = function(e) { ... };
var blob = new Blob([xhr.response], {type: 'image/png'});
writer.write(blob);
}, onError);
}, onError);
}, onError);
};
xhr.send();
Datei in mehrere Teile aufteilen und die einzelnen Teile hochladen
Mit den Datei-APIs können wir den Aufwand für den Upload einer großen Datei minimieren. Dabei wird der Upload in mehrere Blöcke aufgeteilt, für jeden Teil ein XHR-Objekt erstellt und die Datei auf dem Server zusammengesetzt. Das ist ähnlich wie bei Gmail, wo große Anhänge so schnell hochgeladen werden. Mit dieser Methode kann auch das HTTP-Anfragelimit von 32 MB der Google App Engine umgangen werden.
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(blobOrFile);
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
var blob = this.files[0];
const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
const SIZE = blob.size;
var start = 0;
var end = BYTES_PER_CHUNK;
while(start < SIZE) {
upload(blob.slice(start, end));
start = end;
end = start + BYTES_PER_CHUNK;
}
}, false);
})();
Der Code zum Rekonstruieren der Datei auf dem Server wird hier nicht angezeigt.
Verweise
- XMLHttpRequest Level 2-Spezifikation
- Cross-Origin Resource Sharing (CORS)-Spezifikation
- File API-Spezifikation
- FileSystem API-Spezifikation