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. 

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

Посилання ред.