Xmlhttprequest

Пример использования

План работы с объектом XMLHttpRequest можно представить следующим образом:

  1. Создание экземпляра объекта XMLHttpRequest
  2. Открытие соединения
  3. Установка обработчика события (нужно делать после открытия и до отправки в IE)
  4. Отправка запроса.

Создание экземпляра объекта XMLHttpRequest.

На этой стадии необходима отдельная реализация для разных браузеров. Конструкция создания объекта отличается: в IE 5 — IE 6 она реализована через ActiveXObject, а в остальных браузерах (IE 7 и выше, Mozilla, Opera, Chrome, Netscape и Safari) — как встроенный объект типа XMLHttpRequest.

Вызов для ранних версий Internet Explorer выглядит так:

var req = new ActiveXObject("Microsoft.XMLHTTP");

В остальных браузерах:

var req = new XMLHttpRequest();

То есть, для обеспечения кросс-браузерности кода, нужно лишь проверять наличие объектов window.XMLHttpRequest и window.ActiveXObject, и, в зависимости от того, какой есть, тот и применять.

В качестве универсального решения предлагается использование такой функции:

function createRequestObject() {
  if (typeof XMLHttpRequest === 'undefined') {
    XMLHttpRequest = function() {
      try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
        catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
        catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP"); }
        catch(e) {}
      try { return new ActiveXObject("Microsoft.XMLHTTP"); }
        catch(e) {}
      throw new Error("This browser does not support XMLHttpRequest.");
    };
  }
  return new XMLHttpRequest();
}

Установка обработчика событий, открытие соединения и отправка запросов

Эти вызовы выглядят так:

req.open(<"GET"|"POST"|...>, <url>]);
req.onreadystatechange = processReqChange;

Где:

  • <«GET»|«POST»|…> — запроса. Допускаются: DELETE, GET, HEAD, OPTIONS, POST, PUT.
  • <url> — адрес запроса.
  • <asyncFlag> — флаг, определяющий, использовать ли асинхронный запрос. По умолчанию, установлен в true.
  • <user>, <password> — логин и пароль, соответственно. Указываются при необходимости.

После определения всех параметров запроса его остается только отправить. Делается это методом send(). При отправке GET-запроса для версии без ActiveX необходимо указать параметр null, в остальных случаях можно не указывать никаких параметров. Не будет ошибкой, если для GET всегда будет указан параметр null:

req.send(null);

После этого начинает работать вышеуказанный обработчик событий. Он — фактически основная часть программы. В обработчике обычно происходит перехват всех возможных кодов состояния запроса и вызов соответствующих действий, а также перехват возможных ошибок.
Пример кода с этими двумя функциями:


var req;

function loadXMLDoc(url)
{
req = null;
if (window.XMLHttpRequest) {
try {
req = new XMLHttpRequest();
} catch (e){}
} else if (window.ActiveXObject) {
try {
req = new ActiveXObject(‘Msxml2.XMLHTTP’);
} catch (e){
try {
req = new ActiveXObject(‘Microsoft.XMLHTTP’);
} catch (e){}
}
}

if (req) {
req.open(«GET», url, true);
req.onreadystatechange = processReqChange;
req.send(null);
}
}

function processReqChange()
{
try { // Важно!
// только при состоянии «complete»
if (req.readyState == 4) {
// для статуса «OK»
if (req.status == 200) {
// обработка ответа
} else {
alert(«Не удалось получить данные:\n» +
req.statusText);
}
}
}
catch( e ) {
// alert(‘Ошибка: ‘ + e.description);
// В связи с багом XMLHttpRequest в Firefox приходится отлавливать ошибку
// Bugzilla Bug 238559 XMLHttpRequest needs a way to report networking errors
// https://bugzilla.mozilla.org/show_bug.cgi?id=238559
}
}

Определение и применение

JavaScript свойство readyState объекта XMLHttpRequest возвращает состояние объекта XMLHttpRequest. Объект может находиться в следующих состояниях:

Значение Состояние Описание
UNSENT Объект XMLHttpRequest был создан, метод open() объекта не вызывался.
1 OPENED Метод open() объекта был вызван. Во время этого состояния заголовки запросов могут быть установлены с помощью метода setRequestHeader() и метод send() может быть вызван, который инициирует отправку запроса.
2 HEADERS_RECEIVED Метод send() объекта был вызван, заголовки и статус доступны.
3 Происходит загрузка тела ответа сервера. Если значение свойства responseType соответствует значению «text» или пустой строке, то значение свойства responseText будет содержать частичные данные.
4 DONE Операция завершена. Это может означать, что передача данных была завершена успешно или не удалась.

Обращаю Ваше внимание на то, что названия состояний отличаются в браузерах Internet Explorer в версиях ниже 11. Вместо UNSENT (0), OPENED (1), HEADERS_RECEIVED (2), (3) и DONE (4) используются такие названия как:

  • READYSTATE_UNINITIALIZED (0).
  • READYSTATE_LOADING (1).
  • READYSTATE_LOADED (2).
  • READYSTATE_INTERACTIVE (3).
  • READYSTATE_COMPLETE (4).

Как это устроено

Если мы хотим хра­нить дан­ные на сер­ве­ре и отправ­лять их туда в любой момент, нам нуж­но дей­ство­вать так:

  1. Собрать дан­ные в JSON-формат.
  2. Упа­ко­вать их в спе­ци­аль­ный запрос.
  3. Встро­ен­ны­ми сред­ства­ми JavaScript отпра­вить этот запрос на сер­вер по нуж­но­му адресу.
  4. Что­бы наш запрос был при­нят, по это­му адре­су на сер­ве­ре дол­жен нахо­дить­ся скрипт, кото­рый уме­ет рабо­тать с таки­ми запросами.
  5. А что­бы сер­вер в прин­ци­пе отве­чал на какие-то запро­сы, нам нуж­но его это­му обучить.

Пер­вые три пунк­та сде­ла­ем на кли­ен­те — нашей HTML-странице, а скрипт и настрой­ки — на сер­ве­ре. Скрипт будем писать на PHP, поэто­му, если не зна­е­те, что это и как с этим рабо­тать, — почи­тай­те.

Что­бы было про­ще, мы отпра­вим и обра­бо­та­ем на сер­ве­ре совсем малень­кий JSON — в нём все­го две пары «имя: зна­че­ние», но даже со слож­ным запро­сом всё будет рабо­тать так же.

Контроль безопасности

Кросс-доменные запросы проходят специальный контроль безопасности, цель которого – не дать злым хакерам завоевать интернет.

Серьёзно. Разработчики стандарта предусмотрели все заслоны, чтобы «злой хакер» не смог, воспользовавшись новым стандартом, сделать что-то принципиально отличное от того, что и так мог раньше и, таким образом, «сломать» какой-нибудь сервер, работающий по-старому стандарту и не ожидающий ничего принципиально нового.

Давайте, на минуточку, вообразим, что появился стандарт, который даёт, без ограничений, возможность делать любой странице HTTP-запросы куда угодно, какие угодно.

Как сможет этим воспользоваться злой хакер?

Он сделает свой сайт, например и заманит туда посетителя (а может посетитель попадёт на «злонамеренную» страницу и по ошибке – не так важно). Когда посетитель зайдёт на , он автоматически запустит JS-скрипт на странице

Этот скрипт сделает HTTP-запрос на почтовый сервер, к примеру,. А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками

Когда посетитель зайдёт на , он автоматически запустит JS-скрипт на странице. Этот скрипт сделает HTTP-запрос на почтовый сервер, к примеру, . А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками.

Поэтому хакер сможет написать на код, который, сделав GET-запрос на , получит информацию из почтового ящика посетителя. Проанализирует её, сделает ещё пачку POST-запросов для отправки писем от имени посетителя. Затем настанет очередь онлайн-банка и так далее.

Спецификация CORS налагает специальные ограничения на запросы, которые призваны не допустить подобного апокалипсиса.

Запросы в ней делятся на два вида.

считаются запросы, если они удовлетворяют следующим двум условиям:

  1. : GET, POST или HEAD
  2. – только из списка:
  • со значением , или .

«Непростыми» считаются все остальные, например, запрос с методом или с заголовком не подходит под ограничения выше.

Принципиальная разница между ними заключается в том, что «простой» запрос можно сформировать и отправить на сервер и без XMLHttpRequest, например при помощи HTML-формы.

То есть, злой хакер на странице и до появления CORS мог отправить произвольный GET-запрос куда угодно. Например, если создать и добавить в документ элемент , то браузер сделает GET-запрос на этот URL.

Аналогично, злой хакер и ранее мог на своей странице объявить и, при помощи JavaScript, отправить HTML-форму с методом GET/POST и кодировкой . А значит, даже старый сервер наверняка предусматривает возможность таких атак и умеет от них защищаться.

А вот запросы с нестандартными заголовками или с методом таким образом не создать. Поэтому старый сервер может быть к ним не готов. Или, к примеру, он может полагать, что такие запросы веб-страница в принципе не умеет присылать, значит они пришли из привилегированного приложения, и дать им слишком много прав.

Поэтому при посылке «непростых» запросов нужно специальным образом спросить у сервера, согласен ли он в принципе на подобные кросс-доменные запросы или нет? И, если сервер не ответит, что согласен – значит, нет.

В спецификации CORS, как мы увидим далее, есть много деталей, но все они объединены единым принципом: новые возможности доступны только с явного согласия сервера (по умолчанию – нет).

Другие ресурсы

События и также срабатывают и для других ресурсов, а вообще, для любых ресурсов, у которых есть внешний .

Например:

let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)

img.onload = function() {
  alert(`Изображение загружено, размеры ${img.width}x${img.height}`);
};

img.onerror = function() {
  alert("Ошибка во время загрузки изображения");
};

Однако есть некоторые особенности:

  • Большинство ресурсов начинают загружаться после их добавления в документ. За исключением тега . Изображения начинают загружаться, когда получают .
  • Для событие срабатывает по окончании загрузки как в случае успеха, так и в случае ошибки.

Такое поведение сложилось по историческим причинам.

Отправка данных

Возможность очень важна, но она совершенно бесполезна, если эти данные нельзя отправить обратно (на сервер). До недавнего времени в можно было отправлять только данные или (XML). Ситуация изменилась. Переработанный метод позволяет отправлять данные любых типов: , , , , и . Примеры в этой части раздела иллюстрируют отправку данных каждого из этих типов.

Отправка строковых данных: 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');

Как видите, ничего нового. Хотя правая часть несколько отличается. В ней есть строка . Впрочем, ее отсутствие не меняет результат.

Отправка данных форм: xhr.send(FormData)

Многие из нас привыкли пользоваться плагинами jQuery и другими библиотеками для отправки форм AJAX. Вместо них можно использовать  – еще один новый тип данных в рамках технологии XHR2. Тип очень удобен для динамического создания HTML-элементов с помощью 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);
}

По сути, мы просто динамически создаем элемент и добавляем в нее поля с помощью метода append.

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

<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-форма может содержать файлы (например, ). Объект тоже поддерживает эту возможность. Достаточно просто прикрепить файлы, и браузер выполнит запрос при вызове метода .

function uploadFiles(url, files) {
  var formData = new FormData();

  for (var i = 0, file; file = files; ++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').addEventListener('change', function(e) {
  uploadFiles('/server', this.files);
}, false);

Отправка файла или объекта Blob: xhr.send(Blob)

С помощью XHR также можно отправить или объект . Следует помнить, что являются объектами , поэтому в наших примерах между ними нет разницы.

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

<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 также можно отправлять объекты .

function sendArrayBuffer() {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };

  var uInt8Array = new Uint8Array();

  xhr.send(uInt8Array.buffer);
}

§Real-Time Notifications and Delivery

XHR enables a simple and efficient way to synchronize client updates
with the server: whenever necessary, an XHR request is dispatched by the
client to update the appropriate data on the server. However, the same
problem, but in reverse, is much harder. If data is updated on the
server, how does the server notify the client?

HTTP does not provide any way for the server to initiate a new
connection to the client. As a result, to receive real-time
notifications, the client must either poll the server for updates or
leverage a streaming transport to allow the server to push new
notifications as they become available. Unfortunately, as we saw in the
preceding section, support for XHR streaming is limited, which leaves us
with XHR polling.

«Real-time» has different meanings for different applications: some
applications demand submillisecond overhead, while others may be just
fine with delays measured in minutes. To determine the optimal
transport, first define clear latency and overhead targets for your
application!

CORS для простых запросов

В кросс-доменный запрос браузер автоматически добавляет заголовок , содержащий домен, с которого осуществлён запрос.

В случае запроса на с заголовки будут примерно такие:

Сервер должен, со своей стороны, ответить специальными заголовками, разрешает ли он такой запрос к себе.

Если сервер разрешает кросс-доменный запрос с этого домена – он должен добавить к ответу заголовок , содержащий домен запроса (в данном случае «javascript.ru») или звёздочку .

Только при наличии такого заголовка в ответе – браузер сочтёт запрос успешным, а иначе JavaScript получит ошибку.

То есть, ответ сервера может быть примерно таким:

Если нет, то браузер считает, что разрешение не получено, и завершает запрос с ошибкой.

При таких запросах не передаются куки и заголовки HTTP-авторизации. Параметры и в методе игнорируются. Мы рассмотрим, как разрешить их передачу, чуть далее.

Что может сделать хакер, используя такие запросы?

Описанные выше ограничения приводят к тому, что запрос полностью безопасен.

Действительно, злая страница может сформировать любой GET/POST-запрос и отправить его, но без разрешения сервера ответа она не получит.

А без ответа такой запрос, по сути, эквивалентен отправке формы GET/POST, причём без авторизации.

Ограничения IE9-

В IE9- используется , который представляет собой урезанный .

На него действуют ограничения:

  • Протокол нужно сохранять: запросы допустимы с HTTP на HTTP, с HTTPS на HTTPS. Другие протоколы запрещены.
  • Метод имеет только два параметра. Он всегда асинхронный.
  • Ряд возможностей современного стандарта недоступны, в частности:
    • Недоступны методы, кроме GET или POST.
    • Нельзя добавлять свои заголовки, даже нельзя указать свой для запроса, он всегда .
    • Нельзя включить передачу кук и данных HTTP-авторизации.
  • В IE8 в режиме просмотра InPrivate кросс-доменные запросы не работают.

Современный стандарт XMLHttpRequest предусматривает средства для преодоления этих ограничений, но на момент выхода IE8 они ещё не были проработаны, поэтому их не реализовали. А IE9 исправил некоторые ошибки, но в общем не добавил ничего нового.

Поэтому на сайтах, которые хотят поддерживать IE9-, то на практике кросс-доменные запросы редко используют, предпочитая другие способы кросс-доменной коммуникации. Например, динамически создаваемый тег или вспомогательный с другого домена. Мы разберём эти подходы в последующих главах.

Как разрешить кросс-доменные запросы от доверенного сайта в IE9-?

Разрешить кросс-доменные запросы для «доверенных» сайтов можно в настройках IE, во вкладке «Безопасность», включив пункт «Доступ к источникам данных за пределами домена».

Обычно это делается для зоны «Надёжные узлы», после чего в неё вносится доверенный сайт. Теперь он может делать кросс-доменные запросы .

Этот способ можно применить для корпоративных сайтов, а также в тех случаях, когда посетитель заведомо вам доверяет, но почему-то (компьютер на работе, админ запрещает ставить другой браузер?) хочет использовать именно IE. Например, он может предлагаться в качестве дополнительной инструкции «как заставить этот сервис работать под IE».

В IE разрешён другой порт

В кросс-доменные ограничения IE не включён порт.

То есть, можно сделать запрос с на , и в IE он не будет считаться кросс-доменным.

Это позволяет решить некоторые задачи, связанные с взаимодействием различных сервисов в рамках одного сайта. Но только для IE.

Расширенные возможности, описанные далее, поддерживаются всеми современными браузерами, кроме IE9-.

POST с multipart/form-data

Сделать POST-запрос в кодировке multipart/form-data можно и через XMLHttpRequest.

Достаточно указать в заголовке Content-Type кодировку и границу, и далее сформировать тело запроса, удовлетворяющее требованиям кодировки.

Пример кода для того же запроса, что и раньше, теперь в кодировке multipart/form-data:

var data = { name: 'Виктор', surname: 'Цой' }; 
var boundary = String(Math.random()).slice(2); 
var boundaryMiddle = '--' + boundary + '\r\n'; 
var boundaryLast = '--' + boundary + '--\r\n' 
var body = ; for (var key in data) { 
// добавление поля 
body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data + '\r\n'); 
} 
body = body.join(boundaryMiddle) + boundaryLast; 
// Тело запроса готово, отправляем 
var xhr = new XMLHttpRequest();
xhr.open('POST', '/submit', true); 
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); 
xhr.onreadystatechange = function() { 
if (this.readyState != 4) return; 
alert( this.responseText ); } 
xhr.send(body);

Тело запроса будет иметь вид, описанный выше, то есть поля через разделитель.

Отправка файла

Можно создать запрос, который сервер воспримет как загрузку файла.

Для добавления файла нужно использовать тот же код, что выше, модифицировав заголовки перед полем, которое является файлом, так:

Content-Disposition: form-data; name="myfile"; filename="pic.jpg" Content-Type: image/jpeg (пустая строка) содержимое файла

Other signatures


  • a simple string instead of the options. In this case, a GET request will be made to that url.


  • the above may also be called with the standard set of options.

The module has convience functions attached that will make requests with the given method.
Each function is named after its method, with the exception of which is called for compatibility.

The method shorthands may be combined with the url-first form of for succinct and descriptive requests. For example,

xhr.post('/post-to-me',function(err,resp){console.log(resp.body)})

or

xhr.del('/delete-me',{ headers{ my'auth'}},function(err,resp){console.log(resp.statusCode);})

Запросы от имени пользователя

По умолчанию браузер не передаёт с запросом куки и авторизующие заголовки.

Чтобы браузер передал вместе с запросом куки и HTTP-авторизацию, нужно поставить запросу :

Далее – всё как обычно, дополнительных действий со стороны клиента не требуется.

Такой с куками, естественно, требует от сервера больше разрешений, чем «анонимный».

Поэтому для запросов с предусмотрено дополнительное подтверждение со стороны сервера.

При запросе с сервер должен вернуть уже не один, а два заголовка:

Пример заголовков:

Использование звёздочки в при этом запрещено.

Если этих заголовков не будет, то браузер не даст JavaScript’у доступ к ответу сервера.

Пишем PHP-скрипт для сервера

Зада­ча скрип­та пока будет очень про­стой — ему нуж­но будет полу­чить наши дан­ные и пока­зать, что всё при­шло как нуж­но. В PHP уже встро­е­на коман­да, кото­рая раз­би­ра­ет JSON-строку на состав­ля­ю­щие, поэто­му весь скрипт будет зани­мать три строчки:

Для полу­че­ния дан­ных наш PHP-скрипт исполь­зу­ет стан­дарт­ную коман­ду file_get_contents(«php://input»). Она про­сто ждёт, когда что-то при­ле­тит, и отправ­ля­ет резуль­тат в выбран­ную пере­мен­ную. Даль­ше её мож­но разо­брать как JSON-объект коман­дой json_decode() и рабо­тать с дан­ны­ми напрямую.

Послед­няя коман­да echo отправ­ля­ет в ответ то, что напи­са­но в двой­ных кавыч­ках. Там мы обра­ща­ем­ся к пере­мен­ной $data, где хра­нят­ся при­слан­ные дан­ные. Имен­но этот ответ будет обра­ба­ты­вать скрипт на JavaScript, в функ­ции xhr.onreadystatechange, кото­рую мы про­пи­са­ли раньше.

Сам код нам нуж­но сохра­нить как json.php и поло­жить в пап­ку /projects/json/ на нашем сай­те — так мы про­пи­са­ли в скрип­те на JavaScript.

§Uploading Data with XHR

Uploading data via XHR is just as simple and efficient for all data
types. In fact, the code is effectively the same, with the only
difference that we also pass in a data object when calling
send() on the XHR request. The rest is handled by the browser:

var xhr = new XMLHttpRequest();
xhr.open('POST','/upload');
xhr.onload = function() { ... };
xhr.send("text string"); 

var formData = new FormData(); 
formData.append('id', 123456);
formData.append('topic', 'performance');

var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function() { ... };
xhr.send(formData); 

var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function() { ... };
var uInt8Array = new Uint8Array(); 
xhr.send(uInt8Array.buffer); 
  1. Upload a simple text string to the server

  2. Create a dynamic form via FormData API

  3. Upload multipart/form-data object to the server

  4. Create a typed array (ArrayBuffer) of unsigned, 8-bit integers

  5. Upload chunk of bytes to the server

The XHR send() method accepts one of ,
, , ,
, or objects, automatically
performs the appropriate encoding, sets the appropriate HTTP
content-type, and dispatches the request. Need to send a binary blob or
upload a file provided by the user? Simple: grab a reference to the
object and pass it to XHR. In fact, with a little extra work, we can also
split a large file into smaller chunks:

var blob = ...; 

const BYTES_PER_CHUNK = 1024 * 1024; 
const SIZE = blob.size;

var start = 0;
var end = BYTES_PER_CHUNK;

while(start < SIZE) { 
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload');
  xhr.onload = function() { ... };

  xhr.setRequestHeader('Content-Range', start+'-'+end+'/'+SIZE); 
  xhr.send(blob.slice(start, end)); 

  start = end;
  end = start + BYTES_PER_CHUNK;
}
  1. An arbitrary blob of data (binary or text)

  2. Set chunk size to 1 MB

  3. Iterate over provided data in 1MB increments

  4. Advertise the uploaded range of data (start-end/total)

  5. Upload 1 MB slice of data via XHR

XHR does not support request streaming, which means that we must
provide the full payload when calling send(). However, this
example illustrates a simple application workaround: the file is split
and uploaded in chunks via multiple XHR requests. This implementation
pattern is by no means a replacement for a true request streaming API,
but it is nonetheless a viable solution for some applications.

Объект XMLHttpRequest

Объект XMLHttpRequest (или, сокращенно, XHR) дает возможность браузеру делать HTTP-запросы к серверу без перезагрузки страницы.

Несмотря на слово XML в названии, XMLHttpRequest может работать с данными в любом текстовом формате, и даже c бинарными данными. Использовать его очень просто.

Кроссбраузерное создание объекта запроса

В зависимости от браузера, код для создания объекта может быть разный.
Кроссбраузерная функция создания XMLHttpRequest:

function getXmlHttp(){
  var xmlhttp;
  try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
      xmlhttp = false;
    }
  }
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }
  return xmlhttp;
}

Функция тупо перебирает возможные внутренние реализации и возвращает начальный объект XMLHttpRequest. Существует и масса других рабочих кроссбраузерных функций, однако все они по сути делают то же самое.

Свойства объекта XMLHttpRequest

Ссылается на функцию-обработчик состояний запроса. В некоторых браузерах функция имеет аргумент — событие. Не используйте его, он совершенно лишний.

responseText

Текст ответа сервера. Полный текст есть только при readyState=4, ряд браузеров дают доступ к полученной части ответа сервера при readyState=3.

responseXML

Ответ сервера в виде XML, при readyState=4.

Это свойство хранит объект типа XML document, с которым можно обращаться так же, как с обычным document. Например,

var authorElem = xmlhttp.responseXML.getElementById('author');

Чтобы браузер распарсил ответ сервера в свойство responseXML, в ответе должен быть заголовок Content-Type: text/xml.

Иначе свойство responseXML будет равно null.

status

Для HTTP-запросов — статусный код ответа сервера: 200 — OK, 404 — Not Found, и т.п. Браузер Internet Explorer может также присвоить status код ошибки WinInet,
например 12029 для ошибки «cannot connect».

Запросы по протоколам FTP, FILE:// не возвращают статуса, поэтому нормальным для них является status=0.

Synchronous requests

If in the method the third parameter is set to , the request is made synchronously.

In other words, JavaScript execution pauses at and resumes when the response is received. Somewhat like or commands.

Here’s the rewritten example, the 3rd parameter of is :

It might look good, but synchronous calls are used rarely, because they block in-page JavaScript till the loading is complete. In some browsers it becomes impossible to scroll. If a synchronous call takes too much time, the browser may suggest to close the “hanging” webpage.

Many advanced capabilities of , like requesting from another domain or specifying a timeout, are unavailable for synchronous requests. Also, as you can see, no progress indication.

Because of all that, synchronous requests are used very sparingly, almost never. We won’t talk about them any more.

Response Type

We can use property to set the response format:

  • (default) – get as string,
  • – get as string,
  • – get as (for binary data, see chapter ArrayBuffer, binary arrays),
  • – get as (for binary data, see chapter Blob),
  • – get as XML document (can use XPath and other XML methods) or HTML document (based on the MIME type of the received data),
  • – get as JSON (parsed automatically).

For example, let’s get the response as JSON:

Please note:

In the old scripts you may also find and even properties.

They exist for historical reasons, to get either a string or XML document. Nowadays, we should set the format in and get as demonstrated above.

Основы

XMLHttpRequest имеет два режима работы: синхронный и асинхронный.

Сначала рассмотрим асинхронный, так как в большинстве случаев используется именно он.

Чтобы сделать запрос, нам нужно выполнить три шага:

  1. Создать .

  2. Инициализировать его.

    Этот метод обычно вызывается сразу после . В него передаются основные параметры запроса:

    • – HTTP-метод. Обычно это или .
    • – URL, куда отправляется запрос: строка, может быть и объект URL.
    • – если указать , тогда запрос будет выполнен синхронно, это мы рассмотрим чуть позже.
    • , – логин и пароль для базовой HTTP-авторизации (если требуется).

    Заметим, что вызов , вопреки своему названию, не открывает соединение. Он лишь конфигурирует запрос, но непосредственно отсылается запрос только лишь после вызова .

  3. Послать запрос.

    Этот метод устанавливает соединение и отсылает запрос к серверу. Необязательный параметр содержит тело запроса.

    Некоторые типы запросов, такие как , не имеют тела. А некоторые, как, например, , используют , чтобы отправлять данные на сервер. Мы позже увидим примеры.

  4. Слушать события на , чтобы получить ответ.

    Три наиболее используемых события:

    • – происходит, когда получен какой-либо ответ, включая ответы с HTTP-ошибкой, например 404.
    • – когда запрос не может быть выполнен, например, нет соединения или невалидный URL.
    • – происходит периодически во время загрузки ответа, сообщает о прогрессе.

Вот полный пример. Код ниже загружает с сервера и сообщает о прогрессе:

После ответа сервера мы можем получить результат запроса в следующих свойствах :

Код состояния HTTP (число): , , и так далее, может быть в случае, если ошибка не связана с HTTP.
Сообщение о состоянии ответа HTTP (строка): обычно для , для , для , и так далее.
(в старом коде может встречаться как )
Тело ответа сервера.

Мы можем также указать таймаут – промежуток времени, который мы готовы ждать ответ:

Если запрос не успевает выполниться в установленное время, то он прерывается, и происходит событие .

URL с параметрами

Чтобы добавить к URL параметры, вида , и корректно закодировать их, можно использовать объект URL:

Прогресс отправки

Событие срабатывает только на стадии загрузки ответа с сервера.

А именно: если мы отправляем что-то через -запрос, сперва отправит наши данные (тело запроса) на сервер, а потом загрузит ответ сервера. И событие будет срабатывать только во время загрузки ответа.

Если мы отправляем что-то большое, то нас гораздо больше интересует прогресс отправки данных на сервер. Но тут не поможет.

Существует другой объект, без методов, только для отслеживания событий отправки: .

Он генерирует события, похожие на события , но только во время отправки данных на сервер:

  • – начало загрузки данных.
  • – генерируется периодически во время отправки на сервер.
  • – загрузка прервана.
  • – ошибка, не связанная с HTTP.
  • – загрузка успешно завершена.
  • – вышло время, отведённое на загрузку (при установленном свойстве ).
  • – загрузка завершена, вне зависимости от того, как – успешно или нет.

Примеры обработчиков для этих событий:

Пример из реальной жизни: загрузка файла на сервер с индикацией прогресса:

Итоги

  • Все браузеры умеют делать кросс-доменные XMLHttpRequest.
  • Кросс-доменный запрос всегда содержит заголовок Origin с доменом запроса.

Порядок выполнения:

  1. Для запросов с «непростым» методом или особыми заголовками браузер делает предзапрос OPTIONS, указывая их в Access-Control-Request-Method и Access-Control-Request-Headers. Браузер ожидает ответ со статусом 200, без тела, со списком разрешённых методов и заголовков в Access-Control-Allow-Method и Access-Control-Allow-Headers. Дополнительно можно указать Access-Control-Max-Age для того чтобы предзапрос кешировался.
  2. Браузер делает запрос и проверяет, есть ли в ответе Access-Control-Allow-Origin, равный * или Origin. Для запросов с withCredentials может быть только Origin и дополнительно Access-Control-Allow-Credentials: true.
  3. Если все проверки пройдены, то вызывается xhr.onload, иначе xhr.onerror, без деталей ответа.
  4. Дополнительно: названия нестандартных заголовков ответа сервер должен указать в Access-Control-Expose-Headers, если хочет, чтобы клиент мог их прочитать.

Поделиться
Твитнуть
Поделиться

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector