Введение
Одним из важнейших элементов в среде HTML5 является XMLHttpRequest. Строго говоря, этот объект не входит в HTML5. Однако он стал результатом постоянных изменений, вносимых разработчиками браузеров в базовую платформу. XHR2 играет большую роль, так как является неотъемлемой частью современных сложных веб-приложений.
Мало кто знает, что в последнюю версию XHR было добавлено много функций. В XMLHttpRequest Level 2 представлена масса новых возможностей, которые избавят нас от ненужных операций и таких понятий, как кросс-доменные запросы, события хода отправки файлов, а также поддержка загрузки и отправки двоичных данных. Благодаря этому технология AJAX работает в сочетании с новейшими API HTML5: API файловой системы, API веб-аудио и WebGL.
В этом руководстве описываются некоторые из новых возможностей XMLHttpRequest и в особенности те из них, которые необходимы для работы с файлами.
Извлечение данных
Загрузка файла в виде двоичного объекта с помощью XHR всегда была проблемой. С технической точки зрения это было даже невозможно. Один из известных способов заключается в переопределении mime-типа пользовательской кодировкой, как показано ниже.
Ранее содержимое картинки можно было извлечь таким способом:
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();
Этот способ работает, однако элемент responseText вовсе не является большим двоичным объектом (элементом blob). Это двоичная строка, представляющая файл картинки. Мы заставляем сервер вернуть данные в необработанном виде. Хотя этот прием работает, я не рекомендую использовать его. При попытке принудительно перевести данные в нужный формат с помощью манипуляций с кодировкой и строками всегда возникают проблемы.
Указание формата ответа
В предыдущем примере картинка загружалась в виде двоичного файла путем переопределения mime-типа сервера и обработки текста как двоичной строки. Вместо этого воспользуемся новыми возможностями технологии XMLHttpRequest: свойствами responseType и response, позволяющими указать браузеру желаемый формат ответа.
- xhr.responseType
- Прежде чем отправить запрос, необходимо задать для свойства
xhr.responseTypeзначение text, arraybuffer, blob или document. Обратите внимание: если установить значениеxhr.responseType = ''или опустить его, по умолчанию выбирается формат text. - xhr.response
- После успешного выполнения запроса свойство response будет содержать запрошенные данные в формате
DOMString,ArrayBuffer,BlobилиDocumentв соответствии со значениемresponseType.
Переработаем предыдущий пример с использованием этой новой возможности. Теперь мы извлекаем данные картинки в формате ArrayBuffer вместо строки. Передаем буфер в API BlobBuilder и получаем объект Blob.
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var bb = new BlobBuilder();
bb.append(this.response); // Note: not xhr.responseText
var blob = bb.getBlob('image/png');
...
}
};
xhr.send();
Так намного лучше.
Ответы в формате ArrayBuffer
ArrayBuffer – это стандартный контейнер фиксированной длины для двоичных данных. Это очень удобный универсальный буфер для необработанной информации, но его главное достоинство – возможность создавать "представления" исходных данных с помощью типизированных массивов JavaScript. Фактически на базе одного источника ArrayBuffer можно сформировать несколько представлений. Например, можно создать 8-битный целочисленный массив, который использует тот же объект ArrayBuffer, что и 32-битный массив на базе тех же данных. Исходная информация остается неизменной: она просто представляется в разном виде.
В примере ниже мы извлекаем ту же картинку в формате ArrayBuffer, но на этот раз создаем из данных в буфере 8-битный целочисленный массив.
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
Для непосредственной работы с объектами Blob без операций с отдельными байтами файла можно использовать значение 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();
Объект Blob можно использовать по разному: например, сохранить его в индексированной базе данных, записать в файловую систему HTML5 или создать URL элемента Blob, как показано в этом примере.
Отправка данных
Возможность загружать данные в различных форматах очень важна, но она совершенно бесполезна, если эти данные нельзя отправить обратно (на сервер). До недавнего времени в XMLHttpRequest можно было отправлять только данные DOMString или Document (XML). Ситуация изменилась. Переработанный метод send() позволяет отправлять данные любых типов: DOMString, Document, FormData, Blob, File и ArrayBuffer. Примеры в этой части раздела иллюстрируют отправку данных каждого из этих типов.
Отправка строковых данных: 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);
}
sendText2('test string');
Как видите, ничего нового. Хотя правая часть несколько отличается. В ней есть строка responseType='text'. Впрочем, ее отсутствие не меняет результат.
Отправка данных форм: xhr.send(FormData)
Многие из нас привыкли пользоваться плагинами jQuery и другими библиотеками для отправки форм AJAX. Вместо них можно использовать FormData – еще один новый тип данных в рамках технологии XHR2. Тип FormData очень удобен для динамического создания HTML-элементов <form> с помощью JavaScript. Затем эти формы можно отправить с помощью AJAX.
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);
}
По сути, мы просто динамически создаем элемент <form> и добавляем в нее поля <input> с помощью метода append.
При этом форму можно не создавать с нуля. Объекты FormData можно инициализировать с помощью существующих на странице элементов HTMLFormElement. Например:
<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.
}
HTML-форма может содержать файлы (например, <input type="file">). Объект FormData тоже поддерживает эту возможность. Достаточно просто прикрепить файлы, и браузер выполнит запрос multipart/form-data при вызове метода send().
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);
Отправка файла или объекта Blob: xhr.send(Blob)
С помощью XHR также можно отправить файл или объект Blob. Следует помнить, что файлы являются объектами Blob, поэтому в наших примерах между ними нет разницы.
В этом примере мы создаем новый текстовый файл с помощью API BlobBuilder и отправляем этот объект Blob на сервер. Этот код также запускает обработчик, который показывает нам ход отправки файла.
<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);
}
// Take care of vendor prefixes.
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var bb = new BlobBuilder();
bb.append('hello world');
upload(bb.getBlob('text/plain'));
Отправка произвольного набора байтов: xhr.send(ArrayBuffer)
В качестве полезных данных XHR также можно отправлять объекты ArrayBuffer.
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)
С помощью технологии CORS веб-приложения могут выполнять кросс-доменные AJAX-запросы к другим доменам. Сделать это очень просто: достаточно, чтобы сервер отправил необходимый заголовок ответа.
Включение CORS-запросов
Предположим, приложение находится в домене example.com и нужно получить данные из домена www.example2.com. Как правило, при попытке отправить такой AJAX-запрос он не выполняется, а браузер выдает ошибку несоответствия происхождения. Благодаря технологии CORS сайт www.example2.com может разрешить приложению с сайта example.com выполнять запросы путем добавления одного заголовка.
Access-Control-Allow-Origin: http://example.com
Заголовок Access-Control-Allow-Origin можно добавить как для одного сайта, так и для всего домена. Чтобы разрешить отправку запросов из всех доменов, добавьте строку такого вида:
Access-Control-Allow-Origin: *
Фактически на всех страницах этого сайта (html5rocks.com) также используется технология CORS. Запустите инструменты разработчика, и в ответе вы увидите заголовок Access-Control-Allow-Origin:
Access-Control-Allow-Origin на сайте html5rocks.comРазрешить кросс-доменные запросы несложно, поэтому настоятельно рекомендуется включать CORS для общедоступных данных.
Создание кросс-доменного запроса
Если сервер-адресат поддерживает CORS, кросс-доменный запрос ничем не отличается от обычного запроса XMLHttpRequest. Например, вот так можно выполнить запрос с приложения на сервере example.com к серверу www.example2.com:
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();
Практические примеры
Загрузка и сохранение файлов в файловой системе HTML5
Предположим, у вас есть галерея изображений и вы хотите сохранить несколько картинок у себя с помощью файловой системы HTML5. Вы можете запросить эти картинки как объекты ArrayBuffer, создать на основе этих данных объект Blob и записать его с помощью FileWriter.
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 = 'arraybuffer';
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 bb = new BlobBuilder();
bb.append(xhr.response);
writer.write(bb.getBlob('image/png'));
}, onError);
}, onError);
}, onError);
};
xhr.send();
Обратите внимание: для использования этого кода нужно ознакомиться с условиями поддержки браузеров и ограничениями на хранение в руководстве Знакомство с API файловой системы.
Отправка файла по частям
API файлов существенно облегчает отправку больших файлов. Методика такова: крупный файл разбивается на несколько мелких, которые затем отправляются с помощью XHR и собираются обратно на сервере. Примерно так же Gmail быстро отправляет большие прикрепленные файлы. Эта технология также позволяет обойти ограничение Google App Engine: 32 МБ на один HTTP-запрос.
window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder ||
window.BlobBuilder;
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) {
// Note: blob.slice has changed semantics and been prefixed. See http://goo.gl/U9mE5.
if ('mozSlice' in blob) {
var chunk = blob.mozSlice(start, end);
} else {
var chunk = blob.webkitSlice(start, end);
}
upload(chunk);
start = end;
end = start + BYTES_PER_CHUNK;
}
}, false);
})();
Ниже приведен код для сборки файла на сервере.
Проверьте, как он работает.
Полезные ссылки
- Спецификация для XMLHttpRequest версии 2.0.
- Спецификация для технологии обмена ресурсами с запросом происхождения (CORS).
- Спецификация API файлов.
- Спецификация API файловой системы.