JavaScript в браузере. Основы AJAX и COMET

AJAX, Ajax (ˈeɪdʒæks, от англ. Asynchronous Javascript and XML — «асинхронный JavaScript и XML») — подход к построению интерактивных пользовательских интерфейсов веб-приложений, заключающийся в «фоновом» обмене данными браузера с веб-сервером. Т.е. ajax — это не не конкретные инструменты, это методика получения данных.

Получение только части данных, а не обновление всей страницы, работает быстрее и улучшает пользовательский опыт. AJAX может использоваться при необходимости реализации таких структур как infinity scroll, live search, lazyload и т.д.

Данные в AJAX транспортируются чаще всего в одном из следующих форматов:

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

Работа c XMLHttpRequest

Простейший пример использования XMLHttpRequest выглядит так:

var request = new XMLHttpRequest();
request.open('GET', '/data.php');
request.send();
request.onreadystatechange = function() {
    if (request.readyState == 4) {
        if (request.status != 200) {
            console.error( request.status + ': ' + request.statusText );
        } else {
            console.log(request.responseText);
        }
    }
};

У созданного XMLHttpRequest есть следующие методы:

В процессе выполнения запроса и по его завершению заполняются свойства экземпляра XMLHttpRequest:

Список самых популярных кодов ответа сервера:

Экземпляр XMLHttpRequest генерирует следующие события:

Коды состояния AJAX-запроса:

Если есть необходимость установить таймаут ожидания запроса, то для этого задают свойству timeout значение в миллисекундах:

request.timeout = 5000;

Отправка заголовков

Иногда при работе с запросом необходимо отправить HTTP-заголовки или прочитать заголовки ответа сервера. Для этих целей используются следующие методы:

Заголовки можно добавить после конфигурирования запроса, но до его отправки.

На работу с заголовками для XMLHttpRequest наложены ограничения, связанные с безопасностью. Так, нельзя при отправке запроса принудительно указать заголовки Referer, Host, Content-Length и некоторые другие. Такие заголовки браузер формирует сам и не дает для них доступа для Javascript.
Из getResponseHeader и getAllResponseHeaders нельзя прочитать заголовки Set-Cookie и Set-Cookie2.

Кроме "классических" заголовков, подобных тем, которые передаются при отравке обычной формы, можно передать специальный заголовок X-Requested-With со значением XMLHttpRequest для указания серверу, что запрос выполняется с помощью AJAX.

Повторная установка заголовка не перезаписывает, а дополняет его.

XMLHttpRequest для Internet Explorer

IE версий 8 и 9 также поддерживают AJAX-запросы, но возможности по использованию XMLHttpRequest там ограничены. Например, из событий доступно только onreadystatechange. Поэтому для этих версий используют объекты XDomainRequest. Для IE 10 и выше XMLHttpRequest работает полноценно и можно использовать его. Кроссбраузерный код создания запроса может иметь следующий вид:

var request = new XMLHttpRequest();
if(!('onload' in request)) {
    request = new XDomainRequest();
}

Кеширование запросов

AJAX-запросы, как и обычные страницы, могут кешироваться браузером. IE9 и ниже кеширует все запросы по умолчанию. Чтобы предотвратить кеширование можно:

XMLHttpRequest POST, формы и кодировка

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

Кодировка urlencoded

При необходимости передачи, например, кириллистических строк в параметрах GET-запроса в AJAX необходимо вручную закодировать эти строки с помощью urlencoded:

var input = "Инпут";
var url = '/data.php?name=' + encodeURIComponent( input );

При этом все символы, кроме символов латиницы, цифр и знаков базовой пунктуации заменяются на последовательности unicode.

Запросы POST также можно отправлять в кодировке urlencoded. Особенностью POST-запросов является необходимость явного указания кодировки в заголвоке запроса. В случае кодировки urlencoded в заголовок передают значение application/x-www-form-urlencoded. Код такого запроса будет выглядеть как:

var input = "Инпут";
var url = /data.php';
var request = new XMLHttpRequest();
var body = 'input=' + encodeURIComponent(input);
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
request.send(body);

Для данной кодировки всегда используется UTF-8.

Кодировка multipart/form-data

При использовании кодировки urlencoded объем данных при кодировании может сильно увеличиваться, а для больших файлов перекодирование могло бы просходить долго и нагружать браузер. Потому для передачи файлов была создана кодировка multipart/form-data. Но исползовать ее можно не только для передачи файлов, но и для обычныx POST-запросов.

В multipart/form-data передаваемые поля идут в теле запроса друг за другом в исходном виде и разделяются специально сгенерированными разделителями boundary, гарантирующим, что данные не будут смешаны со служебной информацией.

Для отправки POST-запроса в multipart/form-data с помощью AJAX можно воспользоваться следующим кодом:

var input = "Инпут";
var url = /data.php';
var request = new XMLHttpRequest();
var boundary = Math.random().toString().slice(2);
var body = '--' + boundary + '\r\n' +
    'Content-Disposition: form-data; name="input"\r\n\r\n' +
    input +
    '\r\n' +
    '--' + boundary + '--\r\n';
request.open('POST', url);
request.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
request.send(body);

FormData

Севременные браузеры поддерживают формат кодирования форм FormData. Форму можно отправить так:

var myForm = new FormData();
myForm.append("input", "Инпут");
var request = new XMLHttpRequest();
request.open("POST", url);
request.send(myForm);

В конструкторе есть необязательный параметр, в который можно передать DOM-элемент существующей формы. С помощью append можно добавлять новые поля. Такая форма при отправке будет использовать кодировку multipart/form-data.

Передача в формате JSON

Данные для AJAX-запроса можно передавать и как text/plain, и как application/json.

var input = {input: "Инпут"};
var url = /data.php';
var request = new XMLHttpRequest();
var json = JSON.stringify(input);
request.open('POST', url);
request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
request.send(json);

Кросс-доменные запросы

По умолчанию AJAX-запросы доступны только для документов, у которых совпадает протокол, домен и порт с текущим документом. Однако технически есть возможность отправить запрос на другой домен. Такие запросы называются кроссдоменными. Со стороны JavaScript такой запрос может ничем не отличаться от обычного. Единственное ограничение - такой запрос не может быть синхронным. В остальном за доступность кроссдоменного запроса в большей степени отвечает браузер.

За возможность осуществления кроссдоменных запросов отвечает политика CORS (Cross-Origin Resource Sharing). Все запросы в ней разделяются на простые и не простые. К простым относят запросы, которые имеют простой метод - GET, POST или HEAD - и простые заголовки: Accept, Accept-Language, Content-Language и Content-Type со значениями application/x-www-form-urlencoded, multipart/form-data или text/plain. Все остальные запросы считаются "не простыми".

Простые запросы можно сформировать с помощью формы, а для отправки "непростого" нужно использовать AJAX.

Для выполнения простого вопроса через AJAX достаточно совпадения Access-Control-Allow-Origin в заголовке ответа совпадающим с Origin страницы, выполняющей запрос. Если необходимо разрешить все кроссдоменные запросы, то достаточно указать Access-Control-Allow-Origin: * в заголовках сервера.

Для чтения заголовков сервера, отличных от "простых" (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) они должны быть указаны в заголовке ответа Access-Control-Expose-Headers.

Для передачи вместе с запросом еще и данных о cookie пользователя устанавливают свойство withCredentials в true. Чтобы такие данные передались сервер должен отправить заголовок Access-Control-Allow-Credentials: true.

"Непростые" запросы инициализируют дополнительный подзапрос, которые спрашивает у сервера, разрешены ли "непростые" методы или заголовки. Такой запрос отправляется методом OPTIONS, не содержит тела и содержит заголовки Access-Control-Request-Method и Access-Control-Request-Headers. В ответ сервер в заголовках Access-Control-Allow-Method и Access-Control-Allow-Headers перечень допустимых методов и заголовков или ошибку. Дополнительно сервер может в заголовке Access-Control-Max-Age передать время в секугдах, на которое нужно закешировать разрешение.

Индикация прогресса

Запрос XMLHttpRequest состоит из стадии закачки данных на сервер и стадии получения ответа. Ответ сервера меняет соответствующие свойства у экземпляра XMLHttpRequest, а информация о закачке попадает в свойство upload.

Для свойства upload существуют следующие события:

Для стадий закачки и скачивания в событии onprogress для события доступна информация о переданном числе байт (event.loaded) и общем числе байт (event.total). Если информация об общем числе байт недоступна, то в свойстве event.lengthComputable будет false. При закачке на сервер lengthComputable всегда true, т.к. браузер всегда знает размер передаваемых данных.

Событие onprogress происходит при передаче каждого байта, но не чаще раза в 50мс. Обновленный responseText всегда доступен при каждом обновлении прогресса. Request.upload.onprogress указывает на то, что данные были переданы на сервер, но не гарантирует, что данные были получены и обработаны.

WebSocket

WebSocket - простой протокол безопасной кроссдоменной передачи данных между браузером и сервером. Сервер должен поддерживать технологию, чтобы браузер мог "общаться" с ним по этому протоколу.

var socket = new WebSocket('ws://example.com');

Созданный вебсокет имеет следующие события:

Данные по данному протоколу отправляются с помощью socket.send()