XMLHttpRequest2

Одним из важнейших элементов в среде HTML5 является XMLHttpRequest. Строго говоря, этот объект не входит в HTML5. Однако он стал результатом постоянных изменений, вносимых разработчиками браузеров в базовую платформу. XHR2 играет большую роль, так как является неотъемлемой частью современных сложных веб-приложений.

Оглавление:
  1. Извлечение данных
  2. Указание формата ответа
  3. Ответы в формате ArrayBuffer
  4. Ответы в формате Blob
  5. Отправка данных
  6. Отправка строковых данных: xhr.send(DOMString)
  7. Отправка данных форм: xhr.send(FormData)
  8. Отправка файла или объекта Blob: xhr.send(Blob)
  9. Отправка произвольного набора байтов: xhr.send(ArrayBuffer)
  10. Обмен ресурсами с запросом CORS
  11. Включить CORS — запрос
  12. Создать кросс-доменный запрос
  13. Практические примеры
  14. Отправка файла по частям
  15. Полезные ссылки

Озеро Самарцы - Отдых в Крыму для всей семьи!
Адрес: Россия, Республика Крым, Белогорский район, с. Зуя
http://samartsy.ru/

Мало кто знает что в последнюю версию XHR было добавлено много функций. В XMLHttpRequest Level 2 представлена масса новых возможностей, которые избавят нас от ненужных операций и таких понятий, как кросс-доменные запросы, события хода отправки файлов, а также поддержка загрузки и отправки двоичных данных. Благодаря этому технология AJAX работает в сочетании с новейшими API HTML5: файловой системы, веб-аудио и WebGL.

В этом руководстве описываются некоторые из новых возможностей XMLHttpRequest, которые необходимы для работы с файлами.

К началу

Извлечение данных

Загрузка файла в виде двоичного объекта с помощью XHR всегда была проблемой. С технической точки зрения — это было даже невозможно. Один из известных способов заключается в переопределении mime-type пользовательской кодировкой, как показано ниже.

Ранее картинку можно было извлечь таким способом:

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.

При этом элемент form можно не создавать с нуля. Объекты FormData можно инициализировать с помощью существующих на странице элементов HTMLFormElement.

Например:

<form id="myform" action="/server" name="myform">
 <input type="text" name="username" value="johndoe" />
 <input type="number" name="id" value="123456" />
 <input onclick="return sendForm(this.form);" type="submit" />
</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 также можно отправить file или объект Blob. Следует помнить, что файлы являются объектами Blob, поэтому в наших примерах между ними нет разницы.

В этом примере мы создаем новый текстовый файл с помощью API BlobBuilder и отправляем этот объект Blob на сервер. Этот код также запускает обработчик, который показывает нам ход отправки файла.

<progress value="0" max="100">0%</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); }

Обмен ресурсами с запросом 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: *

Запустите инструменты разработчика, и в ответе вы увидите заголовок Access-Control-Allow-Origin:

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();
Заголовок Access-Control-Allow-Origin
Заголовок Access-Control-Allow-Origin

Разрешить кросс-доменные запросы несложно, поэтому настоятельно рекомендуется включать 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();

Обратите внимание: для использования этого кода нужно ознакомиться с условиями поддержки браузеров и ограничениями на хранение в документации о файловой системе.

К началу

Отправка файла по частям

Filesystem API существенно облегчает отправку больших файлов. Методика такова: крупный файл разбивается на несколько мелких, которые затем отправляются с помощью XHR и собираются обратно в один на сервере. Примерно так же почта Gmail быстро отправляет большие прикрепленные файлы. Эта технология также позволяет обойти ограничение Google App Engine: 32 МБ на один HTTP-запрос.

indow.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);

})();

Ниже приведен код для сборки файла на сервере.

Проверьте, как он работает.

#bytes/chunk:

К началу

Полезные ссылки