XMLHttpRequest

XMLHttpRequest — API-запит вебклієнта (браузера) до вебсервера за протоколом HTTP у фоновому режимі, для мов програмування JavaScript, JScript, VBScript і подібних. Використовується для синхронного або асинхронного обміну інформацією в довільному текстовому форматі (наприклад, XML, JSON, HTML). Дозволяє здійснювати HTTP-запити до віддаленого сервера без потреби перезавантажувати сторінку. Застосування XMLHttpRequest справляє враження «миттєвої» відповіді сервера, у порівнянні з класичними методом перезавантаження всієї сторінки для оновлення представленої на ній інформації.

XMLHttpRequest є невід'ємною частиною технології AJAX і використовується багатьма сайтами для створення динамічних вебзастосунків, що швидко реагують на запити користувача. Наприклад XMLHTTP використовується такими сайтами як Gmail, Google Suggest, MSN Virtual Earth та іншими. XMLHTTP працює лише з файлами, розташованими на тому ж домені, з якої завантажено сторінку. Як і у випадку JavaScript, це зроблено з метою забезпечення безпеки користувача (як захист від атаки, що має назву «міжсайтові сценарії», англ. cross-site scripting).

Історія[ред. | ред. код]

Вперше був реалізований компанією Microsoft, з'явившись в Internet Explorer 5.0 у вигляді об'єкта ActiveX, доступного через JavaScript, JScript, VBScript — скриптові мови, що підтримуються браузером. Програмісти проекту Mozilla потім розробили сумісну версію, під назвою XMLHttpRequest[1], в Mozilla 1.0. Надалі ця можливість також була реалізована компаніями Apple починаючи з Safari 1.2, спорідненим браузером Konqueror, компанією Opera Software починаючи з Opera 8.01, і ймовірно іншими.

Оскільки оригінальний XMLHttpRequest в IE5 та IE6 є об'єктом ActiveX, його неможливо розширити, додавши нові властивості і методи, що іноді є незручним обмеженням. Це обмеження було знято в реалізації Mozilla — XMLHttpRequest є повноцінним об'єктом JavaScript. Починаючи з IE7 Microsoft теж почав дотримуватися рекомендованого w3c визначення запиту.

Методи класу XMLHttpRequest[ред. | ред. код]

Метод Опис
abort() скасовує поточний запит
getAllResponseHeaders() повертає повний список HTTP-заголовків у вигляді рядка
getResponseHeader (headerName) повертає значення вказаного заголовка
open (method, URL, async, userName, password) визначає метод, URL і інші необов'язкові параметри запиту;
параметр async визначає, чи відбувається робота в асинхронному режимі
send (content) відправляє запит на сервер
setRequestHeader (label, value) додає HTTP-заголовок до запиту
overrideMimeType (mimeType) дозволяє вказати MIME-тип документа, якщо сервер його не передав або передав неправильно.
Увага: метод відсутній в Internet Explorer

Властивості класу XMLHttpRequest[ред. | ред. код]

Властивість Опис
onreadystatechange обробник події, яка відбувається при кожній зміні стану об'єкта (необхідний для асинхронного режиму)
readyState повертає поточний стан об'єкта (0 — не ініціалізовано, 1 — відкрито, 2 — відправлення даних, 3 — отримання даних і 4 — дані завантажено)
responseText текст відповіді на запит
responseXML текст відповіді на запит в вигляді XML, котрий пізніше може бути розібраний методами DOM
status повертає HTTP-стан у вигляді числа (404 — «Not Found, Не найдено», 200 — «OK» тощо)
statusText повертає стан у вигляді рядка («Not Found», «OK» тощо)

Приклад використання[ред. | ред. код]

План роботи з об'єктом XMLHttpRequest можна представити так:

  1. Створення об'єкта XMLHttpRequest
  2. Встановлення для нього обробника події onreadystatechange
  3. Відкриття з'єднання з вказівкою типу запиту, URL і інших параметрів.
  4. Безпосередньо відправлення запиту.

Створення екземпляра класу XMLHttpRequest[ред. | ред. код]

Перший пункт: створення екземпляра класу XMLHttpRequest. Конструкція створення об'єкта відрізняється в залежності від версії браузера: у IE 5 та IE 6 вона реалізована через ActiveXObject, а в решті браузерах (IE 7, Mozilla, Opera, Netscape і Safari) — як вбудований об'єкт типу XMLHttpRequest.

Отже, виклик для ранніх версій Internet Explorer:

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

У ранніх версіях Internet Explorer (до IE7) рекомендується використовувати:

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

і для решти:

 var req = new XMLHttpRequest(); 

Тобто, для забезпечення кросс-браузерності нашого коду, потрібно лише перевіряти наявність об'єктів window.XMLHttpRequest і window.ActiveXObject, і застосовувати присутній. Як універсальне рішення пропонується використання наступної функції:

function createRequestObject() {     if (window.XMLHttpRequest) {         try {             return new XMLHttpRequest();         } catch (e){}     } else if (window.ActiveXObject) {         try {             return new ActiveXObject('Msxml2.XMLHTTP');         } catch (e){}         try {             return new ActiveXObject('Microsoft.XMLHTTP');         } catch (e){}     }     return null; } 

Установлення обробника події[ред. | ред. код]

Наступним кроком є створення обробника подій і відкриття з'єднання. Ці виклики виглядають просто і однаково:

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

Відкриття з'єднання і відправлення[ред. | ред. код]

Після визначення всіх параметрів запиту його залишається тільки відправити. Робиться це функцією send(). Якщо необхідно передати на сервер POST-дані, їх треба підставити як параметр для цієї функції. POST-дані повинні бути згорнуті в URL-закодований рядок (кодування UTF-8). Іншими словами цей рядок матиме вигляд, який ми звикли бачити в командному рядку браузера, при передачі даних командою GET. При відправленні запиту методом 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.onreadystatechange = processReqChange;         req.open("GET", url, true);         req.send(null);     } }  function processReqChange() {     // Тільки в стані "complete"     if (req.readyState == 4) {         // для стану "OK"         if (req.status == 200) {             // Якщо 200 - робимо потрібні дії (404 - не знайдено)         } else {             alert("Не вдалось одержати дані:\n" +                 req.statusText);         }     } } 

Підсумковий код[ред. | ред. код]

Отже, початковий код JavaScript-частини:

var req; var reqTimeout;  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.onreadystatechange = processReqChange;         req.open("GET", url, true);         req.send(null);         reqTimeout = setTimeout("req.abort();", 5000);     } else {         alert("Браузер не підтримує AJAX");     } }  function processReqChange() {     document.form1.state.value = stat(req.readyState);       if (req.readyState == 4) {         clearTimeout(reqTimeout);          document.form1.statusnum.value = req.status;         document.form1.status.value = req.statusText;          // only if "OK"         if (req.status == 200) {             document.form1.response.value=req.responseText;         } else {             alert("Не вдалося отримати дані:\n" + req.statusText);         }     } }  function stat(n) {   switch (n) {     case 0:       return "не ініціалізовано";     break;      case 1:       return "Завантаження...";     break;      case 2:       return "Завантажено";     break;      case 3:       return "В процесі...";     break;      case 4:       return "Виконано";     break;      default:       return "Невідомий стан";   } }  function requestdata(params) {   loadXMLDoc('examples/httpreq.php'+params); } 

Тепер — HTML-форма:

<form name=form1> <table width=100% style="font-size: 100%"> <tr><td width=30% valign=top> Стан запиту <td width=70%> <input size=25 disabled type=text name=state value=""> <tr><td valign=top>Код стану <td><input disabled size=2 type=text name=statusnum value=""> <input disabled size=19 type=text name=status value=""> <tr><td valign=top>Дані від сервера <td><textarea rows=6 name=response></textarea> <tr><td>Рядок GET-запиту<td> <input type=text name=getparams value="?"> <input type=button onclick="requestdata(getparams.value);" value="GET"> </table> </form> 

І наостанок, PHP файл:

<?php header("Content-type: text/plain; charset=windows-1251"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Expires: -1");   echo "Hello world!\n\n";  if (isset($a)) {   for ($i=1; $i < 10000; $i++)   {     echo 'Це тестовий рядок. ';     if (($i % 1000) == 0) flush();   } }  if (count($_GET) > 0) {   echo "\n\nПередано GET'ом\n"; print_r($_GET); } ?> 

Кодування[ред. | ред. код]

Всі параметри GET/POST, що йдуть на сервер, окрім випадку multipart/form-data, кодуються по різному в різних браузерах. Зокрема, Firefox користується стандартним кодом URL, Opera вдається до кодування в UTF-8, IE7 передає кирилицю не кодуючи, як є. Тому треба бути уважним, інформація про спосіб кодування присутня в заголовках запиту. Наприклад, в PHP їх потрібно за потреби перекодувати функцією iconv. Єдино, можна бути певним, що латиниця не перекодовується в будь-якому випадку, і якщо є можливість залишитися в рамках латиниці, це позбавить програміста від додаткових клопотів.

Відповідь сервера браузер сприймає в тому кодуванні, яке вказане в заголовку відповіді Content-Type. Тобто, знову ж таки, в PHP, щоб браузер сприйняв відповідь в Windows-1251, потрібно послати заголовок типу:

 header(Content-Type: text/plain; charset=windows-1251); 

Або ж, це має зробити сервер.

Відомі проблеми[ред. | ред. код]

Проблема з кешуванням в Microsoft Internet Explorer[ред. | ред. код]

Internet Explorer кешує GET-запити. Ті автори, які незнайомі з кешуванням HTTP, сподіваються, що GET-запити не кешуються, або що кеш може бути обійдений, як у разі натиснення кнопки оновлення. У деяких ситуаціях уникнення кешування дійсно є помилкою. Одним з рішень є використання методу POST, який ніколи не кешується; проте він призначений для інших операцій. Іншим рішенням є використання методу запиту GET, що включає унікальний рядок запиту з кожним викликом, як показано на прикладі нижче.

req.open("GET", "xmlprovider.php?hash=" + Math.random()); 

або установки заголовка Expires на минулу дату у вашому скрипті, який генерує вміст XML. У PHP це буде так:

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // disable IE caching header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); 

У сервлетах Java це буде так:

response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setDateHeader("Expires", 0); 

Інакше можна примусити об'єкт XMLHttpRequest завжди витягати новий вміст, не використовуючи кеш.

req.open("GET", "xmlprovider.php"); req.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); req.send(null); 

Важливо відмітити, що всі ці методики повинні використовуватися у разі, коли кешування заважає. В основному ж краще отримати переваги в швидкості при кешуванні, можливо комбінуючи зі спеціально вказаними датами модифікації або іншими доречними заголовками на сервері так, щоб максимально використовувати кешування без отримання неправильних результатів.

Повторне використання об'єкта XmlHttpRequest[ред. | ред. код]

В Internet Explorer, якщо open() викликаний після установки onreadystatechange, може бути проблема з повторним використанням цього XmlHttpRequest. Щоб використовувати наново XmlHttpRequest, спочатку викликайте метод open(), а потім — призначайте onreadystatechange. Це потрібно тому, що IE неявно очищає об'єкт XmlHttpRequest в методі open(), якщо його стан «completed».

Викликати abort() для перенаправлення запиту на іншій URL не потрібно, навіть якщо поточний запит ще не завершився.

Витоки пам'яті[ред. | ред. код]

В Internet Explorer об'єкт XmlHttpRequest належить середовищу DOM/COM, а Javascript-функція — середовищу Javascript. Виклик req.onreadystatechange = function() { … } неявний круговий зв'язок: req посилається на функцію через onreadystatechange, а функція, через область видимості — бачить (посилається на) req.

Неможливість виявити і обірвати такий зв'язок в багатьох (до IE 6,7 редакцій червня 2007?) версіях Internet Explorer приводить до того, що XmlHttpRequest разом з відповіддю сервера, функція-обробник, і все замикання міцно осідають в пам'яті до перезавантаження браузера. Щоб цього уникнути, ряд фреймворків (YUI, dojo…) взагалі не ставлять onreadystatechange, а натомість через setTimeout перевіряють його readyState кожні 10 мілісекунд. Це розриває кругову зв'язку req <-> onreadystatechange, і витік пам'яті не загрожує навіть в найбільш глючних браузерах.

Обмеження безпеки[ред. | ред. код]

Кросс-доменний XMLHttpRequest[ред. | ред. код]

Для обмеження XmlHttpRequest використовується філософія «Same Origin Policy» — «Правило одного джерела». Воно дуже просте — кожен сайт працює в своїй пісочниці. Запит можна робити тільки на адреси з тим же протоколом, доменом, портом, що і поточна сторінка. Тобто, із сторінки на адресі http://site.com не можна зробити XmlHttpRequest на адресу https://web.archive.org/web/20190617134849/http://www.site.com/, http://site.com:81[недоступне посилання з червня 2019] або https://web.archive.org/web/20030621190843/http://www.othersite.com/.

Це створює проблему, якщо хочеться узяти вміст з іншого сайту. Як правило, в цьому випадку замість XmlHttpRequest використовуються інші засоби, наприклад, завантаження через динамічно створюваний тег <script>. Але, здебільшого, XmlHttpRequest є зручнішим.

Проксі[ред. | ред. код]

Найпростіший спосіб обійти це обмеження — проксування. Припустимо, ми хочемо зробити запит з http://site.com [Архівовано 17 червня 2019 у Wayback Machine.] на https://web.archive.org/web/20150508130049/http://remote.com/get.html. Замість вказівки remote.com у методі open(), там ставиться URL виду http://site.com/proxy/remote.com/get.html[недоступне посилання з червня 2019], а сервер на site.com вже обробляє цей запит, як треба.

Якщо remote.com знаходиться на іншому сервері, то серверу site.com доведеться проксувати відвідувачеві як запит, так і відповідь. При цьому, зрозуміло, site.com не отримає куки remote.com, тому з цієї точки зору для користувача все безпечно.

Використання наддомену[ред. | ред. код]

Часто кросбраузерні запити — це спосіб обійти обмеження в 2 одночасних з'єднання до одного домену-порту. Спосіб використовувати два різних сервера в спілкуванні з відвідувачем. Крос-доменні запити між наддоменами https://web.archive.org/web/20110102121034/http://a.site.com/, http://b.site.com[недоступне посилання з червня 2019] на http://site.com [Архівовано 17 червня 2019 у Wayback Machine.] допустимі, через властивість document.domain, яке треба встановити в site.com

// на сторінці а.site.com  document.domain="site.com";  // все, тепер можу робити XmlHttpRequest на site.com req.open("POST", "http://site.com/giveme.php") 

Будь-які запити допустимі між сайтами, що знаходяться в довіреній (trusted) зоні Internet Explorer. Отже, внутрішній корпоративний портал може бути у всіх в цій зоні, і робити запити до будь-яких сайтів.

Ще один хитрий підхід називається XHRIframeProxy, і дозволяє робити XmlHttpRequest до будь-яких доменів за допомогою хитрого iframe-хака.

В плагінах Google Chrome[ред. | ред. код]

Пишучи аддон до браузера Google Chrome можна дозволити робити запити на довільні сервери, записавши їхні адреси в manifest.json[2]

{   "name": "My extension",   ...   "permissions": [     "http://www.google.com/"   ],   ... } 

Примітки[ред. | ред. код]

  1. Mozilla намагалася зберегти максимальну сумісність із оригіналом, були вилучені лише пропрієтарні назви Microsoft та ActiveX
  2. Архівована копія. Архів оригіналу за 8 лютого 2010. Процитовано 27 липня 2010.{{cite web}}: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання)

Див. також[ред. | ред. код]

Посилання[ред. | ред. код]