Javascript метод xmlhttprequest.open()
Содержание:
Определение и применение
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).
Old Browsers (IE5 and IE6)
Old versions of Internet Explorer (IE5 and IE6) do not support the
XMLHttpRequest object.
To handle IE5 and IE6,
check if the browser supports the XMLHttpRequest object, or else create an ActiveXObject:
Example
if (window.XMLHttpRequest) { // code for modern browsers
xmlhttp = new XMLHttpRequest();
}
else { // code for old IE browsers
xmlhttp = new ActiveXObject(«Microsoft.XMLHTTP»);
}
Old versions of Internet Explorer (IE5 and IE6) do not support the DOMParser object.
To handle IE5 and IE6,
check if the browser supports the DOMParser object, or else create an ActiveXObject:
Example
if (window.DOMParser) { // code for modern browsers
parser = new DOMParser();
xmlDoc = parser.parseFromString(text,»text/xml»);
}
else {
// code for old IE browsersxmlDoc = new ActiveXObject(«Microsoft.XMLDOM»);
xmlDoc.async = false;
xmlDoc.loadXML(text);
}
Ограничения безопасности. Кросс-доменный XMLHttpRequest
Для ограничения XmlHttpRequest используется философия «Same Origin Policy». Она очень проста — каждый сайт в своей песочнице. Запрос можно делать только на адреса
с тем же протоколом, доменом, портом, что и текущая страница.
Т.е, со страницы на адресе http://site.com нельзя сделать XmlHttpRequest на адрес https://site.com, http://site.com:81 или http://othersite.com
Это создает проблему, если хочется взять контент с другого сайта. Как правило, в этом случае вместо XmlHttpRequest используются другие средства, например, загрузка через
динамически создаваемый тег
Проксирование
Самый простой способ обойти это ограничение — проксирование. Допустим, мы хотим сделать запрос с http://site.com на http://remote.com/get.html.
Чтобы обойти ограничение, вместо указания remote.com в методе open(), там ставится специальный URL вида http://site.com/proxy/remote.com/get.html. Так что запрос приходит на наш веб-сервер, который проксирует его на сервер site.com, который в свою очередь обрабатывает этот запрос, как нужно.
Если remote.com находится на другом сервере, то серверу site.com придется проксировать посетителю как запрос, так и ответ. При этом, разумеется, никак не будут задействованы куки remote.com, так что не получится отдельной авторизации, учета пользователей или чтото в этом роде с отдельными куками.
Проксирование настраивается соответствующим модулем (mod_proxy, proxy module и т.п.) веб-сервера для всех адресов, начинающихся на /proxy.
Например, при использовании web-сервера Apache, для проксирования нужны директивы ProxyPass, ProxyPassReverse. Кроме того, доступны еще модули, которые по необходимости правят урлы, разархивируют контент
Использование наддомена
Часто кроссбраузерные запросы — это
- Способ обойти ограничения в 2 одновременных соединения к одному домену-порту.
- Способ использовать два разных сервера в общении с посетителем. Например, на chat.site.ru — чат-демон, на www.site.ru — веб-сервер.
Кросс-доменные запросы с поддомена типа http://a.site.com, http://b.site.com на базовый домен site.com допустимы при использовании свойства document.domain, которое надо установить в site.com
// на странице a.site.com ... document.domain='site.com' ... // все, теперь могу делать XmlHttpRequest на site.com xmlhttp.open(..'http://site.com/feedme.php'..)
Запрос на старый домен
В браузере Internet Explorer, чтобы сделать запрос на старый домен a.site.com, нужно вернуть свойство document.domain обратно. В остальных браузерах это приводит к ошибке, поэтому можно оформить код типа такого:
var oldDomain = document.domain document.domain = "site.com" try { // для IE, в остальных браузерах ошибка... document.domain = oldDomain; } catch(e) { /* ... но в них все и так работает */ } //... работаем с a.site.com ...
Same origin и фреймы
Приятным бонусом свойства document.domain является возможность коммуникации между фреймами/ифреймами на одном домене.
То есть, например, если
- во фрейме с адреса http://a.site.com установлен document.domain=’site.com’,
- на фрейме с адреса http://b.site.com установлен домен document.domain=’site.com’
- на фрейме с адреса http://site.com установлен (обязательно!) домен document.domain=’site.com’
То эти три фрейма могут свободно общаться посредством javascript и XmlHttpRequest.
Обычно такая коммуникация используется при создании чатов/событий с сервера, когда на site.com находится основной веб-сервер, а на chat.site.com висит чат-демон.
Internet Explorer trusted zone
Любые запросы допустимы между сайтами, находящимися в доверенной (trusted) зоне Internet Explorer. Так что, внутренний корпоративный портал может быть у всех пользователей в этой зоне, и он сможет делать запросы к любым сайтам.
XhrIframeProxy
Еще один хитрый подход называется , и позволяет делать XmlHttpRequest к любым доменам при помощи хитрого iframe-хака. Он основан на том, что фреймы с разных доменов могут читать и менять друг у друга anchor, т.е часть адреса после решетки ‘#’. За счет этого организуется специальный протокол, по которому «проксируется» XmlHttpRequest.
Этот метод, в принципе, вполне жизнеспособен, особенно для небольшого объема данных.
Кросс-доменные запросы в FF3/IE8/Opera9..
В спецификации HTML 5 предусмотрены кросс-доменные запросы .
Создатели Firefox и Opera реализовали этот вариант, см. например MDC: .
Разработчики IE8 пошли другим путем и предлагают .
Оба способа вполне жизнеспособны и уже пригодны для использования в интранет-приложениях, когда на всех машинах администратор ставит одинаковый браузер, например, Firefox 3 ?
GET и POST-запросы. Кодировка.
Во время обычного submit’а формы браузер сам кодирует значения полей и составляет тело GET/POST-запроса для посылки на сервер. При работе через XmlHttpRequest, это нужно делать самим, в javascript-коде. Большинство проблем и вопросов здесь связано с непониманием, где и какое кодирование нужно осуществлять.
Вначале рассмотрим общее кодирование запросов, ниже — правильную работу с русским языком для windows-1251.
Существуют два вида кодирования HTTP-запроса. Основной — urlencoded, он же — стандартное кодирование URL. Пробел представляется как %20, русские буквы и большинство спецсимволов кодируются, английские буквы и дефис оставляются как есть.
Способ, которым следует кодировать данные формы при submit’е, задается в ее HTML-таге:
<form method="get"> // метод GET с кодировкой по умолчанию <form method="post" enctype="application/x-www-form-urlencoded"> // enctype явно задает кодировку <form method="post"> // метод POST с кодировкой по умолчанию (urlencoded, как и предыдущая форма)
Если форма submit’ится обычным образом, то браузер сам кодирует (urlencode) название и значение каждого поля данных ( и т.п.) и отсылает форму на сервер в закодированном виде.
Формируя XmlHttpRequest, мы должны формировать запрос «руками», кодируя поля функцией .
Конечно, пропускать через encodeURIComponent стоит только те переменные, в которых могут быть спецсимволы или не английские буквы, т.е которые и будут как раз закодированы.
Например, для посылки GET-запроса с произвольными параметрами name и surname, их необходимо закодировать вот так:
// Пример с GET ... var params = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname) xmlhttp.open("GET", '/script.html?'+params, true) ... xmlhttp.send(null)
В методе POST параметры передаются не в URL, а в теле, посылаемом через . Поэтому нужно указывать не в адресе, а при вызове
Кроме того, при POST обязателен заголовок Content-Type, содержащий кодировку. Это указание для сервера — как обрабатывать (раскодировать) пришедший запрос.
// Пример с POST ... var params = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname) xmlhttp.open("POST", '/script.html', true) xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') ... xmlhttp.send(params)
Заголовки Content-Length, Connection в POST-запросах, хотя их и содержат некоторые «руководства», обычно не нужны. Используйте их, только если Вы действительно знаете, что делаете.
Запросы multipart/form-data
Второй способ кодирования — это отсутствие кодирования. Например, кодировать не нужно для пересылки файлов. Он указывается в форме (только для POST) так:
<form method="post" enctype="multipart/form-data">
В этом случае при отправке данных на сервер ничего не кодируется. А сервер, со своей стороны, посмотрев на Content-Type(=multipart/form-data), поймет, что пришло.
Возможности XmlHttpRequest позволяют создать запрос с любым телом. Например, можно вручную сделать POST-запрос, загружающий на сервер файл. Функционал создания
таких запросов есть, в частности, во фреймворке . Но можно реализовать его и самому, прочитав о нужном формате тела POST и заголовках.
Кодировка (языковая)
Если Вы используете только UTF-8 — пропустите эту секцию.
Все идущие на сервер параметры GET/POST, кроме случая multipart/form-data, кодируются в UTF-8. Не в кодировке страницы, а именно в UTF-8. Поэтому, например, в PHP их нужно при необходимости перекодировать функцией iconv.
<?php // ajax.php $name = iconv('UTF8','CP1251',$_GET); ?>
С другой стороны, ответ с сервера браузер воспринимает именно в той кодировке, которая указана в заголовке ответа Content-Type. Т.е, опять же, в PHP, чтобы браузер воспринял ответ в windows-1251 и нормально отобразил данные на странице в windows-1251,
нужно послать заголовок с кодировкой в php-коде, например так:
<?php // ajax.php header('Content-Type: text/plain; charset=utf-8'); ?>
Или же, такой заголовок должен добавить сервер. Например, в apache автоматически добавляется кодировка опцией:
# в конфиге апача AddDefaultCharset windows-1251
Summary
Typical code of the GET-request with :
There are actually more events, the lists them (in the lifecycle order):
- – the request has started.
- – a data packet of the response has arrived, the whole response body at the moment is in .
- – the request was canceled by the call .
- – connection error has occurred, e.g. wrong domain name. Doesn’t happen for HTTP-errors like 404.
- – the request has finished successfully.
- – the request was canceled due to timeout (only happens if it was set).
- – triggers after , , or .
The , , , and events are mutually exclusive. Only one of them may happen.
The most used events are load completion (), load failure (), or we can use a single handler and check the properties of the request object to see what happened.
We’ve already seen another event: . Historically, it appeared long ago, before the specification settled. Nowadays, there’s no need to use it, we can replace it with newer events, but it can often be found in older scripts.
If we need to track uploading specifically, then we should listen to same events on object.
Отправляем данные на сервер
Обычно, чтобы отправить данные на сервер, нужно делать запрос. Вы нажимаете кнопку на странице, браузер уходит думать, ждёт ответа сервера и рисует вам новую страницу в соответствии с этим ответом. Грубо говоря, запрос на сервер заставляет вашу страницу перезагружаться.
К счастью, инженеры давно придумали, как отправлять данные на сервер, не перезагружая страницу. Для этого используют JavaScript.
Чтобы отправить запрос, нам понадобится встроенный объект XMLHttpRequest(). Вот как мы вводим его в повествование:
// создаём новый экземпляр запроса XHRlet xhr = new XMLHttpRequest();
Для запроса нам нужно знать адрес, куда будем отправлять наш JSON — это и есть адрес нашего скрипта, который мы напишем позже. Мы будем писать его на PHP, поэтому заранее можем придумать ему имя и адрес, где он будет лежать: http://mihailmaximov.ru/projects/json/json.php.
// адрес, куда мы отправим нашу JSON-строкуurl = «http://mihailmaximov.ru/projects/json/json.php»;
Когда мы знаем адрес, мы можем открыть соединение, чтобы сервер был готов принять наши данные. Это значит, что мы пока ничего ценного туда не посылаем, а просто предупреждаем сервер, что скоро что-то прилетит:
// открываем соединение. url — это переменная с нашим адресомxhr.open(«POST», url, true);
Теперь напишем заголовок запроса, чтобы сервер понимал, какие данные мы ему пришлём и как ему их обрабатывать. Так как у нас JSON, то это и пропишем в заголовке:
// устанавливаем заголовок — выбираем тип контента, который отправится на сервер, в нашем случае мы явно пишем, что это JSONsetRequestHeader(«Content-Type», «application/json»);
Чуть ниже сразу пропишем поведение скрипта на случай ответа сервера. Сервер должен обработать наши данные, вернуть ответ, а мы должны этот ответ поймать и вывести на страницу:
Последнее, что нам осталось сделать, — вытащить наши введённые данные из полей, собрать из них JSON и отправить на сервер:
// преобразуем наши данные JSON в строку var data = JSON.stringify({ «name»: name.value, «lastname»: lastname.value }); // когда всё готово, отправляем JSON на сервер xhr.send(data);
Готовый скрипт на странице
Upload progress
The event triggers only on the downloading stage.
That is: if we something, first uploads our data (the request body), then downloads the response.
If we’re uploading something big, then we’re surely more interested in tracking the upload progress. But doesn’t help here.
There’s another object, without methods, exclusively to track upload events: .
It generates events, similar to , but triggers them solely on uploading:
- – upload started.
- – triggers periodically during the upload.
- – upload aborted.
- – non-HTTP error.
- – upload finished successfully.
- – upload timed out (if property is set).
- – upload finished with either success or error.
Example of handlers:
Here’s a real-life example: file upload with progress indication:
Использование XMLHTTPRequest
Различают два использования XmlHttpRequest. Первое — самое простое, синхронное.
Синхронный XMLHttpRequest
В этом примере через XMLHTTPRequest с сервера запрашивается страница http://example.org/, и текст ответа сервера показывается через alert().
var xmlhttp = getXmlHttp() xmlhttp.open('GET', '/xhr/test.html', false); xmlhttp.send(null); if(xmlhttp.status == 200) { alert(xmlhttp.responseText); }
Здесь сначала создается запрос, задается открытие () синхронного соединение с адресом /xhr/test.html и запрос отсылается с null,
т.е без данных.
При синхронном запросе браузер «подвисает» и ждет на строчке 3, пока сервер не ответит на запрос. Когда ответ получен — выполняется строка 4, код ответа сравнивается с 200 (ОК), и при помощи alert
печатается текст ответа сервера. Все максимально просто.
Свойство responseText получит такой же текст страницы, как браузер, если бы Вы в перешли на /xhr/test.html. Для сервера
GET-запрос через XmlHttpRequest ничем не отличается от обычного перехода на страницу.
Асинхронный XMLHttpRequest
Этот пример делает то же самое, но асинхронно, т.е браузер не ждет выполнения запроса для продолжения скрипта. Вместо этого к свойству onreadystatechange подвешивается
функция, которую запрос вызовет сам, когда получит ответ с сервера.
var xmlhttp = getXmlHttp() xmlhttp.open('GET', '/xhr/test.html', true); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { if(xmlhttp.status == 200) { alert(xmlhttp.responseText); } } }; xmlhttp.send(null);
Асинхронность включается третьим параметром функции open. В отличие от синхронного запроса, функция send() не останавливает
выполнение скрипта, а просто отправляет запрос.
Запрос xmlhttp регулярно отчитывается о своем состоянии через вызов функции xmlhttp.onreadystatechange. Состояние под номером 4 означает конец выполнения, поэтому функция-обработчик
при каждом вызове проверяет — не настало ли это состояние.
Вообще, список состояний readyState такой:
- 0 — Unitialized
- 1 —
- 2 — Loaded
- 3 — Interactive
- 4 — Complete
Состояния 0-2 вообще не используются.
Вызов функции с состоянием Interactive в теории должен происходить каждый раз при получении очередной порции данных от сервера.
Это могло бы быть удобным для обработки ответа по частям, но Internet Explorer не дает доступа к уже полученной части ответа.
Firefox дает такой доступ, но для обработки запроса по частям состояние Interactive все равно неудобно из-за сложностей обнаружения ошибок соединения.
Поэтому Interactive тоже не используется.
На практике используется только последнее, Complete.
Если хотите углубиться в тонкости багов браузеров c readyState, отличными от 4, то многие из них рассмотрены в статье на.
Не используйте синхронные запросы
Синхронные запросы применяются только в крайнем случае, когда кровь из носу необходимо дождаться ответа сервера до продолжения скрипта. В 999 случаях из 1000
можно использовать асинхронные запросы. При этом общий алгоритм такой:
- Делаем асинхронный запрос
- Рисуем анимированную картинку или просто запись типа «Loading…»
- В onreadystatechange при достижении состояния 4 убираем Loading и, в зависимости от status вызываем обработку ответа или ошибки.
Кроме того, иногда полезно ставить ограничение на время запроса. Например, хочется генерировать ошибку, если запрос висит более 10 секунд.
Для этого сразу после send() через setTimeout ставится вызов обработчика ошибки, который очищается при получении ответа и обрывает запрос с генерацией ошибки,
если истекли 10 секунд.
Таймаут на синхронный запрос ставить нельзя, браузер может висеть долго-долго.. А вот на асинхронный — пожалуйста.
Этот пример демонстрирует такой таймаут.
var xmlhttp = getXmlHttp() xmlhttp.open("POST", "/someurl", true); xmlhttp.onreadystatechange=function(){ if (xmlhttp.readyState != 4) return clearTimeout(timeout) // очистить таймаут при наступлении readyState 4 if (xmlhttp.status == 200) { // Все ок ... alert(xmlhttp.responseText); ... } else { handleError(xmlhttp.statusText) // вызвать обработчик ошибки с текстом ответа } } xmlhttp.send("a=5&b=4"); // Таймаут 10 секунд var timeout = setTimeout( function(){ xmlhttp.abort(); handleError("Time over") }, 10000); function handleError(message) { // обработчик ошибки ... alert("Ошибка: "+message) ... }
Свойства интерфейса
Свойство | Описание | Chrome | Firefox | Opera | Safari | IExplorer | Edge |
---|---|---|---|---|---|---|---|
onreadystatechange | Содержит обработчик событий, вызываемый при запуске события readystatechange, то есть при каждом изменении свойства readyState объекта XMLHttpRequest. | Да | Да | Да | Да | Да | Да |
readyState | Возвращает состояние объекта XMLHttpRequest. | Да | Да | Да | Да | Да | Да |
response | Свойство возвращает содержимое тела ответа в виде ArrayBuffer, Blob, Document, объекта JavaScript или DOMString в зависимости от значения свойства responseType запроса. | Да | Да | Да | Да | 10.0 | Да |
responseText | Возвращает строковое значение, содержащее ответ на запрос в виде текста, или null, если запрос был неудачным или еще не был отправлен. | Да | Да | Да | 10.0 | Да | Да |
responseType | Возвращает перечисляемое строковое значение, указывающее тип данных, содержащихся в ответе. | 31.0 | 6.0 | 18.0 | 7.0 | 10.0 | Да |
responseURL | Возвращает сериализованный URL ответа или пустую строку, если URL равен null. | 37.0 | 32.0 | 24.0 | 8.0 | Нет | 14.0 |
responseXML | Возвращает документ (Document), содержащий HTML или XML, полученный запросом, или значение null, если запрос был неудачным, еще не был отправлен, или если данные не могут быть проанализированы как XML или HTML. | Да | Да | Да | Да | Да | Да |
status | Возвращает числовой код состояния HTTP ответа, полученный от сервера. | Да | Да | Да | Да | Да | Да |
statusText | Возвращает строковое значение, которое содержит строку ответа, возвращенную сервером HTTP. | Да | Да | Да | Да | Да | Да |
timeout | Соответствует количеству миллисекунд, которые может занять запрос перед автоматическим завершением (предельное время ожидания ответа в миллисекундах). | Да | Да | Да | Да | Да | Да |
upload | Свойство возвращает объект XMLHttpRequestUpload, который определяет набор свойств регистрации обработчиков событий для отслеживания процесса выгрузки тела HTTP запроса. | Да | Да | Да | 10.0 | Да | Да |
withCredentials | Соответствует логическому значению, которое определяет необходимость аутентификации при выполнении междоменного CORS запроса (Cross-origin resource sharing, с англ. «совместное использование ресурсов между разными источниками») и необходимость обработки заголовков cookie в CORS ответах. | Да | Да | Да | Да | Да | Да |
Объект XMLHttpRequest
Краеугольным камнем Ajax является объект XMLHttpRequest. Этот объект чрезвычайно полезен и обманчиво прост. По сути, он позволяет асинхронно отправлять запросы серверу и получать результаты в виде текста. А решение о том, что запрашивать, как обрабатывать запрос на стороне сервера и что возвращать клиенту, возлагается на разработчика.
Хотя современные браузеры предоставляют широкую поддержку объекта XMLHttpRequest, существуют тонкие различия в способе получения к нему доступа. В некоторых браузерах, включая Internet Explorer 7, Firefox, Safari и Opera, объект XMLHttpRequest реализован в виде собственного объекта JavaScript. В версиях, предшествующих Internet Explorer 7, он реализован как объект ActiveX. Вследствие этих различий код JavaScript должен быть достаточно интеллектуальным, чтобы использовать правильный подход при создании экземпляра XMLHttpRequest. Ниже приведен клиентский код JavaScript, который Microsoft применяет при решении этой задачи для клиентской функции обратного вызова:
Отправка запроса
Для отправки запроса с помощью объекта XMLHttpRequest будут использоваться два метода: open() и send().
Метод open() устанавливает вызов — он определяет запрос, который требуется отправить серверу. У него есть два обязательных параметра — тип команды HTTP (GET, POST или PUT) и URL-адрес. Например:
Дополнительно можно предоставить третий параметр, который указывает, должен ли запрос выполняться асинхронно, и еще два параметра, чтобы предоставить информацию об имени пользователя и пароле для аутентификации. Однако применение параметров имени пользователя и пароля маловероятно, поскольку эта информация не может быть безопасно жестко закодирована в коде JavaScript. Клиентский код никогда не может служить подходящим местом для реализации функций обеспечения безопасности.
По умолчанию все запросы, выполняемые с помощью объекта XMLHttpRequest, являются асинхронными, И в основном нет причин изменять это поведение. Если решите выполнять вызов синхронно, с тем же успехом можно инициировать обратную отправку — в конце концов, пользователь будет неспособен сделать что-либо, пока страница заморожена и ожидает ответа. Отсутствие асинхронности означает отсутствие Ajax.
Метод send() отправляет запрос. Если запрос является асинхронным, он выполняет возврат немедленно:
Метод send() принимает единственный необязательный строковый параметр. Его можно применять для предоставления дополнительной информации, отправляемой с запросом, такой как значения, которые отправляются с запросом POST.
Обработка ответа
Очевидно, что был упущен один нюанс. Мы выяснили, как отправить запрос, но как обработать ответ? Секрет в том, чтобы присоединить обработчик события, используя свойство onreadystatechange. Это свойство указывает на клиентскую функцию JavaScript, которая вызывается, когда запрос завершен и данные доступны:
Конечно, обработчик события должен быть присоединен до вызова метода send() для запуска запроса.
Когда ответ возвращен с сервера и функция инициирована, необходимую информацию можно извлечь из объекта xmlRequest через свойства responseText и responseXML. Свойство responseText предоставляет все содержимое как одну длинную строку. Свойство responseXML возвращает содержимое в виде дерева узловых объектов.
Несмотря на то что название Ajax подразумевает XML-содержимое, из сервера можно также вернуть что-нибудь другое, в том числе простой текст. Например, если сервер возвращает единственный фрагмент данных, нет никакого смысла помещать его в полный XML-документ.