Xml httprequest: описание, применение, частые проблемы

Instance Methods


abort() : undefined

getAllResponseHeaders() : String

Returns a string containing all header value pairs from the response.

Example:

var request = new XMLHttpRequest(); request.open(‘GET’, ‘/’, /* async = */ false); request.send(); console.log(request.getAllResponseHeaders());

getResponseHeader(header : String) : String

Returns the value for the specified . Returns if the headers do not contain a value for .

Example:

var request = new XMLHttpRequest(); request.open(‘GET’, ‘/’, /* async = */ false); request.send(); console.log(request.getResponseHeader(‘content-type’));

open(method : String, url : String, ]]) : undefined

Specifies the to read from and the http method (, , , , etc) to use when reading the url. If is , the request will be asynchronous and you should provide an callback to be called when the read completes. In general, it is best to use the asynchronous request so the browser remains responsive while the request is in progress. Call to begin the request.

Example:

var syncRequest = new XMLHttpRequest(); syncRequest.open(‘GET’, ‘/’, /* async = */ false); syncRequest.send();

console.log(‘sync status code: ‘ + syncRequest.status); console.log(‘sync response length: ‘ + syncRequest.responseText.length); console.log(‘sync response head: ‘ + syncRequest.responseText.substring(0, 15) + ‘…’); console.log(»);

var asyncRequest = new XMLHttpRequest(); asyncRequest.open(‘GET’, ‘/’, true); asyncRequest.onload = function() { console.log(‘async status code: ‘ + asyncRequest.status); console.log(‘async response length: ‘ + asyncRequest.responseText.length); console.log(‘async response: ‘ + asyncRequest.responseText.substring(0, 15) + ‘…’); }; asyncRequest.send();

overrideMimeType(mime : String) : undefined

send 6 variants

send() : undefined

send(data : ArrayBuffer) : undefined

Sends the specified data to the server.

send(data : Blob) : undefined

Sends the specified Blob to the server.

send(data : Document) : undefined

Sends the specified document to the server.

send(data : String) : undefined

Sends the specified string to the server.

send(data : FormData) : undefined

Sends the specified FormData to the server.

Example:

<form id=’test-form’> Input1: <input name=’input1′><br> Input2: <input name=’input2′><br> <input type=’submit’> </form> <script> var testForm = document.getElementById(‘test-form’); testForm.onsubmit = function(event) { event.preventDefault();

var request = new XMLHttpRequest(); // POST to httpbin which returns the POST data as JSON request.open(‘POST’, ‘https://httpbin.org/post’, /* async = */ false);

var formData = new FormData(document.getElementById(‘test-form’)); request.send(formData);

console.log(request.response); } </script>

JS Tutorial

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

XMLHttpRequest Object Methods

Method Description
new XMLHttpRequest() Creates a new XMLHttpRequest object
abort() Cancels the current request
getAllResponseHeaders() Returns header information
getResponseHeader() Returns specific header information
open(method,url,async,user,psw) Specifies the requestmethod: the request type GET or POSTurl: the file locationasync: true (asynchronous) or false (synchronous)user: optional user namepsw: optional password
send() Sends the request to the serverUsed for GET requests
send(string) Sends the request to the server.Used for POST requests
setRequestHeader() Adds a label/value pair to the header to be sent

CORS для простых запросов

В кросс-доменный запрос браузер автоматически добавляет заголовок , содержащий домен, с которого осуществлён запрос.

В случае запроса на с заголовки будут примерно такие:

Сервер должен, со своей стороны, ответить специальными заголовками, разрешает ли он такой запрос к себе.

Если сервер разрешает кросс-доменный запрос с этого домена – он должен добавить к ответу заголовок , содержащий домен запроса (в данном случае «javascript.ru») или звёздочку .

Только при наличии такого заголовка в ответе – браузер сочтёт запрос успешным, а иначе JavaScript получит ошибку.

То есть, ответ сервера может быть примерно таким:

Если нет, то браузер считает, что разрешение не получено, и завершает запрос с ошибкой.

При таких запросах не передаются куки и заголовки HTTP-авторизации. Параметры и в методе игнорируются. Мы рассмотрим, как разрешить их передачу, чуть далее.

Что может сделать хакер, используя такие запросы?

Описанные выше ограничения приводят к тому, что запрос полностью безопасен.

Действительно, злая страница может сформировать любой GET/POST-запрос и отправить его, но без разрешения сервера ответа она не получит.

А без ответа такой запрос, по сути, эквивалентен отправке формы GET/POST, причём без авторизации.

HTTP-заголовки

умеет как указывать свои заголовки в запросе, так и читать присланные в ответ.

Для работы с HTTP-заголовками есть 3 метода:

Устанавливает заголовок запроса с именем и значением .

Например:

Ограничения на заголовки

Некоторые заголовки управляются исключительно браузером, например или , а также ряд других. Полный список .


не разрешено изменять их ради безопасности пользователей и для обеспечения корректности HTTP-запроса.

Поставленный заголовок нельзя снять

Ещё одной особенностью является то, что отменить невозможно.

Если заголовок определён, то его нельзя снять. Повторные вызовы лишь добавляют информацию к заголовку, а не перезаписывают его.

Например:

Возвращает значение заголовка ответа (кроме и ).

Например:

Возвращает все заголовки ответа, кроме и .

Заголовки возвращаются в виде единой строки, например:

Между заголовками всегда стоит перевод строки в два символа (независимо от ОС), так что мы можем легко разделить их на отдельные заголовки. Значение заголовка всегда отделено двоеточием с пробелом . Этот формат задан стандартом.

Таким образом, если хочется получить объект с парами заголовок-значение, нам нужно задействовать немного JS.

Вот так (предполагается, что если два заголовка имеют одинаковое имя, то последний перезаписывает предыдущий):

«Непростые» запросы

В кросс-доменном можно указать не только , но и любой другой метод, например , .

Когда-то никто и не думал, что страница сможет сделать такие запросы. Поэтому ряд веб-сервисов написаны в предположении, что «если метод – нестандартный, то это не браузер». Некоторые веб-сервисы даже учитывают это при проверке прав доступа.

Чтобы пресечь любые недопонимания, браузер использует предзапрос в случаях, когда:

  • Если метод – не GET / POST / HEAD.
  • Если заголовок имеет значение отличное от , или , например .
  • Если устанавливаются другие HTTP-заголовки, кроме , , .

…Любое из условий выше ведёт к тому, что браузер сделает два HTTP-запроса.

Первый запрос называется «предзапрос» (английский термин «preflight»). Браузер делает его целиком по своей инициативе, из JavaScript мы о нём ничего не знаем, хотя можем увидеть в инструментах разработчика.

Этот запрос использует метод . Он не содержит тела и содержит название желаемого метода в заголовке , а если добавлены особые заголовки, то и их тоже – в .

Его задача – спросить сервер, разрешает ли он использовать выбранный метод и заголовки.

На этот запрос сервер должен ответить статусом 200, без тела ответа, указав заголовки и, при необходимости, .

Дополнительно он может указать , где – количество секунд, на которые нужно закэшировать разрешение. Тогда при последующих вызовах метода браузер уже не будет делать предзапрос.

Давайте рассмотрим предзапрос на конкретном примере.

Рассмотрим запрос , который используется в протоколе WebDAV для управления файлами через HTTP:

Этот запрос «непростой» по двум причинам (достаточно было бы одной из них):

  1. Метод .
  2. Заголовок .

Поэтому браузер, по своей инициативе, шлёт предварительный запрос :

Обратим внимание на детали:

  • Адрес – тот же, что и у основного запроса: .
  • Стандартные заголовки запроса , , присутствуют.
  • Кросс-доменные специальные заголовки запроса:
    • – домен, с которого сделан запрос.
    • – желаемый метод.
    • – желаемый «непростой» заголовок.

На этот запрос сервер должен ответить статусом 200, указав заголовки и .

Но в протоколе WebDav разрешены многие методы и заголовки, которые имеет смысл сразу перечислить в ответе:

Ответ должен быть без тела, то есть только заголовки.

Браузер видит, что метод – в числе разрешённых и заголовок – тоже, и дальше он шлёт уже основной запрос.

При этом ответ на предзапрос он закэширует на 86400 сек (сутки), так что последующие аналогичные вызовы сразу отправят основной запрос, без .

Основной запрос браузер выполняет уже в «обычном» кросс-доменном режиме:

Ответ сервера, согласно спецификации , может быть примерно таким:

Так как содержит правильный домен, то браузер вызовет и запрос будет завершён.

Использование 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 можно использовать асинхронные запросы. При этом общий алгоритм такой:

  1. Делаем асинхронный запрос
  2. Рисуем анимированную картинку или просто запись типа «Loading…»
  3. В 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)
  ...
}

Using a Callback Function

A callback function is a function passed as a parameter to another function.

If you have more than one AJAX task in a website, you should create one function for executing the object, and one callback function for each AJAX task.

The function call should contain the URL and what function to call when the response is ready.

Example

loadDoc(«url-1», myFunction1); loadDoc(«url-2», myFunction2); function loadDoc(url, cFunction) {   var xhttp;   xhttp = new XMLHttpRequest();   xhttp.onreadystatechange = function() {     if (this.readyState == 4 && this.status == 200) {      cFunction(this);    }   };   xhttp.open(«GET», url, true);   xhttp.send(); } function myFunction1(xhttp) {   // action goes here }

function myFunction2(xhttp) {   // action goes here }

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

Кодировка urlencoded за счёт замены символов на может сильно «раздуть» общий объём пересылаемых данных. Поэтому для пересылки файлов используется другая кодировка: multipart/form-data.

В этой кодировке поля пересылаются одно за другим, через строку-разделитель.

Чтобы использовать этот способ, нужно указать его в атрибуте и метод должен быть POST:

Форма при такой кодировке будет выглядеть примерно так:

…То есть, поля передаются одно за другим, значения не кодируются, а чтобы было чётко понятно, какое значение где – поля разделены случайно сгенерированной строкой, которую называют «boundary» (англ. граница), в примере выше это :

Сервер видит заголовок , читает из него границу и раскодирует поля формы.

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

Однако, никто не мешает использовать эту кодировку всегда для POST запросов. Для GET доступна только urlencoded.

Example Explained

The first line in the example above creates an XMLHttpRequest object:

var xhttp = new XMLHttpRequest();

The onreadystatechange property specifies a function to be executed every time the status of the XMLHttpRequest object changes:

xhttp.onreadystatechange = function()

When readyState property is 4 and the status property is 200, the response is ready:

if (this.readyState == 4 && this.status == 200)

The responseText property returns the server response as a text string.

The text string can be used to update a web page:

document.getElementById(«demo»).innerHTML = xhttp.responseText;

You will learn a lot more about the XMLHttpRequest object in the AJAX chapters of this tutorial.

JS Tutorial

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

Методы объекта XMLHttpRequest¶

open()

Варианты вызова:

Первый параметр method — HTTP-метод. Как правило, используется GET либо POST, хотя доступны и более экзотические, вроде TRACE/DELETE/PUT и т. п.

URL — адрес запроса. Можно использовать не только HTTP/HTTPS, но и другие протоколы, например FTP и . При этом есть ограничения безопасности, так называемая «same origin policy»: запрос со страницы можно отправлять только на тот домен и порт, с которого она пришла.

Ниже это ограничение и способы обхода будут рассмотрены подробнее.

задает асинхронные запросы, эта тема была поднята выше.

, — данные для HTTP-авторизации.

send()

Отсылает запрос. Аргумент — тело запроса. Например, GET-запроса тела нет, поэтому используется , а для POST-запросов тело содержит параметры запроса.

abort()

Вызов этого метода обрывает текущий запрос.

Здесь есть одно НО для браузера Internet Explorer. Успешный вызов на самом деле может не обрывать соединение, а оставлять его в подвешенном состоянии на некоторый таймаут (20-30 секунд). Отловить такие повисшие соединения можно через прокси для отладки, например, Fiddler.

У браузера есть лимит: не более 2 одновременных соединений с одним доменом-портом. Т. е., если два соединения уже висят (и отвиснут по таймауту), то третье открыто не будет, пока одно из них не умрет. Надеюсь, Вы с такой проблемой не столкнетесь. Ее можно обойти использованием кросс-доменных XmlHttpRequest.

setRequestHeader(name, value)

Устанавливает заголовок name запроса со значением value. Если заголовок с таким name уже есть — он заменяется. Например,

The onreadystatechange Event

The readyState property holds the status of the XMLHttpRequest.

The onreadystatechange event is triggered every time the readyState changes.

During a server request, the readyState changes from 0 to 4:

0: request not initialized 1: server connection established 2: request received 3: processing request 4: request finished and response is ready

In the onreadystatechange property, specify a function to be executed when the readyState changes:

xhttp.onreadystatechange = function()

When readyState is 4 and status is 200, the response is ready:


if (this.readyState == 4 && this.status == 200)

Объект XMLHttpRequest

Объект XMLHttpRequest (или, сокращенно, XHR) дает возможность браузеру делать HTTP-запросы к серверу без перезагрузки страницы.

Несмотря на слово XML в названии, XMLHttpRequest может работать с данными в любом текстовом формате, и даже c бинарными данными. Использовать его очень просто.

Кроссбраузерное создание объекта запроса

В зависимости от браузера, код для создания объекта может быть разный. Кроссбраузерная функция создания XMLHttpRequest:

function getXmlHttp(){
  var xmlhttp;
  try {
    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
      xmlhttp = false;
    }
  }
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }
  return xmlhttp;
}

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

Пример использования

План работы с объектом XMLHttpRequest можно представить следующим образом:

  1. Создание экземпляра объекта XMLHttpRequest
  2. Открытие соединения
  3. Установка обработчика события (нужно делать после открытия и до отправки в IE)
  4. Отправка запроса.

Создание экземпляра объекта XMLHttpRequest.

На этой стадии необходима отдельная реализация для разных браузеров. Конструкция создания объекта отличается: в IE 5 — IE 6 она реализована через ActiveXObject, а в остальных браузерах (IE 7 и выше, Mozilla, Opera, Chrome, Netscape и Safari) — как встроенный объект типа XMLHttpRequest.

Вызов для ранних версий Internet Explorer выглядит так:

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

В остальных браузерах:

var req = new XMLHttpRequest();

То есть, для обеспечения кросс-браузерности кода, нужно лишь проверять наличие объектов window.XMLHttpRequest и window.ActiveXObject, и, в зависимости от того, какой есть, тот и применять.

В качестве универсального решения предлагается использование такой функции:

function createRequestObject() {
  if (typeof XMLHttpRequest === 'undefined') {
    XMLHttpRequest = function() {
      try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
        catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
        catch(e) {}
      try { return new ActiveXObject("Msxml2.XMLHTTP"); }
        catch(e) {}
      try { return new ActiveXObject("Microsoft.XMLHTTP"); }
        catch(e) {}
      throw new Error("This browser does not support XMLHttpRequest.");
    };
  }
  return new XMLHttpRequest();
}

Установка обработчика событий, открытие соединения и отправка запросов

Эти вызовы выглядят так:

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

Где:

  • <«GET»|«POST»|…> — запроса. Допускаются: DELETE, GET, HEAD, OPTIONS, POST, PUT.
  • <url> — адрес запроса.
  • <asyncFlag> — флаг, определяющий, использовать ли асинхронный запрос. По умолчанию, установлен в true.
  • <user>, <password> — логин и пароль, соответственно. Указываются при необходимости.

После определения всех параметров запроса его остается только отправить. Делается это методом send(). При отправке 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.open(«GET», url, true); req.onreadystatechange = processReqChange; req.send(null); } }

function processReqChange() { try { // Важно! // только при состоянии «complete» if (req.readyState == 4) { // для статуса «OK» if (req.status == 200) { // обработка ответа } else { alert(«Не удалось получить данные:\n» + req.statusText); } } } catch( e ) { // alert(‘Ошибка: ‘ + e.description); // В связи с багом XMLHttpRequest в Firefox приходится отлавливать ошибку // Bugzilla Bug 238559 XMLHttpRequest needs a way to report networking errors // https://bugzilla.mozilla.org/show_bug.cgi?id=238559 } }

JSON Example

This example reads a menu from myTutorials.txt, and displays the menu in a web page:

JSON Example

<div id=»id01″></div><script>var xmlhttp = new XMLHttpRequest(); var url = «myTutorials.txt»;xmlhttp.onreadystatechange = function() {    if (this.readyState == 4 && this.status == 200) {        var myArr = JSON.parse(this.responseText);        myFunction(myArr);    }};xmlhttp.open(«GET», url, true); xmlhttp.send();function myFunction(arr) {    var out = «»;    var i;    for(i = 0; i < arr.length; i++) {        out += ‘<a href=»‘ + arr.url + ‘»>’ +         arr.display + ‘</a><br>’;    }    document.getElementById(«id01»).innerHTML = out;} </script>

Контроль безопасности

Кросс-доменные запросы проходят специальный контроль безопасности, цель которого – не дать злым хакерам завоевать интернет.

Серьёзно. Разработчики стандарта предусмотрели все заслоны, чтобы «злой хакер» не смог, воспользовавшись новым стандартом, сделать что-то принципиально отличное от того, что и так мог раньше и, таким образом, «сломать» какой-нибудь сервер, работающий по-старому стандарту и не ожидающий ничего принципиально нового.

Давайте, на минуточку, вообразим, что появился стандарт, который даёт, без ограничений, возможность делать любой странице HTTP-запросы куда угодно, какие угодно.

Как сможет этим воспользоваться злой хакер?

Он сделает свой сайт, например и заманит туда посетителя (а может посетитель попадёт на «злонамеренную» страницу и по ошибке – не так важно). Когда посетитель зайдёт на , он автоматически запустит JS-скрипт на странице

Этот скрипт сделает HTTP-запрос на почтовый сервер, к примеру,. А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками

Когда посетитель зайдёт на , он автоматически запустит JS-скрипт на странице. Этот скрипт сделает HTTP-запрос на почтовый сервер, к примеру, . А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками.

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

Спецификация CORS налагает специальные ограничения на запросы, которые призваны не допустить подобного апокалипсиса.

Запросы в ней делятся на два вида.

считаются запросы, если они удовлетворяют следующим двум условиям:

  1. : GET, POST или HEAD
  2. – только из списка:
  • со значением , или .

«Непростыми» считаются все остальные, например, запрос с методом или с заголовком не подходит под ограничения выше.

Принципиальная разница между ними заключается в том, что «простой» запрос можно сформировать и отправить на сервер и без XMLHttpRequest, например при помощи HTML-формы.

То есть, злой хакер на странице и до появления CORS мог отправить произвольный GET-запрос куда угодно. Например, если создать и добавить в документ элемент , то браузер сделает GET-запрос на этот URL.

Аналогично, злой хакер и ранее мог на своей странице объявить и, при помощи JavaScript, отправить HTML-форму с методом GET/POST и кодировкой . А значит, даже старый сервер наверняка предусматривает возможность таких атак и умеет от них защищаться.

А вот запросы с нестандартными заголовками или с методом таким образом не создать. Поэтому старый сервер может быть к ним не готов. Или, к примеру, он может полагать, что такие запросы веб-страница в принципе не умеет присылать, значит они пришли из привилегированного приложения, и дать им слишком много прав.

Поэтому при посылке «непростых» запросов нужно специальным образом спросить у сервера, согласен ли он в принципе на подобные кросс-доменные запросы или нет? И, если сервер не ответит, что согласен – значит, нет.

В спецификации CORS, как мы увидим далее, есть много деталей, но все они объединены единым принципом: новые возможности доступны только с явного согласия сервера (по умолчанию – нет).

XMLHttpRequest Properties and Methods

Method Description
new XMLHttpRequest() Creates a new XMLHttpRequest object
open(method, url, async) Specifies the type of requestmethod: the type of request: GET or POSTurl: the file locationasync: true (asynchronous) or false (synchronous)
send() Sends a request to the server (used for GET)
send(string) Sends a request string to the server (used for POST)
onreadystatechange A function to be called when the readyState property changes
readyState The status of the XMLHttpRequest 0: request not initialized 1: server connection established 2: request received 3: processing request 4: request finished and response is ready
status 200: OK 404: Page not found
responseText The response data as a string
responseXML The response data as XML data

Основы

XMLHttpRequest имеет два режима работы: синхронный и асинхронный.

Сначала рассмотрим асинхронный, так как в большинстве случаев используется именно он.

Чтобы сделать запрос, нам нужно выполнить три шага:

  1. Создать .

  2. Инициализировать его.

    Этот метод обычно вызывается сразу после . В него передаются основные параметры запроса:

    • – HTTP-метод. Обычно это или .
    • – URL, куда отправляется запрос: строка, может быть и объект URL.
    • – если указать , тогда запрос будет выполнен синхронно, это мы рассмотрим чуть позже.
    • , – логин и пароль для базовой HTTP-авторизации (если требуется).

    Заметим, что вызов , вопреки своему названию, не открывает соединение. Он лишь конфигурирует запрос, но непосредственно отсылается запрос только лишь после вызова .

  3. Послать запрос.

    Этот метод устанавливает соединение и отсылает запрос к серверу. Необязательный параметр содержит тело запроса.

    Некоторые типы запросов, такие как , не имеют тела. А некоторые, как, например, , используют , чтобы отправлять данные на сервер. Мы позже увидим примеры.

  4. Слушать события на , чтобы получить ответ.

    Три наиболее используемых события:

    • – происходит, когда получен какой-либо ответ, включая ответы с HTTP-ошибкой, например 404.
    • – когда запрос не может быть выполнен, например, нет соединения или невалидный URL.
    • – происходит периодически во время загрузки ответа, сообщает о прогрессе.

Вот полный пример. Код ниже загружает с сервера и сообщает о прогрессе:

После ответа сервера мы можем получить результат запроса в следующих свойствах :

Код состояния HTTP (число): , , и так далее, может быть в случае, если ошибка не связана с HTTP.
Сообщение о состоянии ответа HTTP (строка): обычно для , для , для , и так далее.
(в старом коде может встречаться как )
Тело ответа сервера.

Мы можем также указать таймаут – промежуток времени, который мы готовы ждать ответ:

Если запрос не успевает выполниться в установленное время, то он прерывается, и происходит событие .

URL с параметрами

Чтобы добавить к URL параметры, вида , и корректно закодировать их, можно использовать объект URL:


С этим читают