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 (посилання) [Архівовано 2010-02-08 у Wayback Machine.]

Див. також

Посилання