Этот сайт использует cookies. Продолжение работы с сайтом означает, что Вы согласны!
Кроссдоменные запросы
Если мы возьмем код листинга 3.14 и отправим запрос на http://site1/ajax.php, находясь на странице http://localhost/test.html, то получим следующее сообщение об ошибке:
TypeError: NetworkError when attempting to fetch resource
Таким образом, по умолчанию мы можем отправить запрос только на тот же самый домен. Чтобы выполнить кроссдоменный запрос нужно в опции mode
объекта запроса указать режим "cors"
(разрешает кроссдоменные запросы; значение по умолчанию):
let url = 'http://site1/ajax.php?txt1=' + encodeURIComponent(txt1);
url += '&txt2=' + encodeURIComponent(txt2);
let response = await fetch(url, {
mode: 'cors'
});
Указать режим недостаточно, ведь значение "cors"
используется по умолчанию. Нужно дополнительно соблюсти политику 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
) и некоторые другие, а также заголовки, отправляемые самим Web-браузером.
При выполнении простого запроса Web-браузер дополнительно отправит заголовок Origin
с названием домена:
Origin: http://localhost
Сервер должен отправить заголовок Access-Control-Allow-Origin
, значением которого может быть домен из заголовка запроса Origin
:
header('Access-Control-Allow-Origin: http://localhost');
или символ *
, означающий все домены:
header('Access-Control-Allow-Origin: *');
В нашем примере запрос соответствует всем условиям простого запроса, поэтому в файле http://site1/ajax.php достаточно добавить вывод заголовка Access-Control-Allow-Origin
, например, после вывода MIME-типа, и кроссдоменный запрос будет успешно выполнен:
// Указываем MIME-тип и кодировку
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: http://localhost');
Если запрос не является простым, то Web-браузер выполнит предварительный запрос методом OPTIONS
. В этом запросе будут следующие заголовки:
Origin
— с названием домена:
Origin: http://localhost
Access-Control-Request-Method
— с методом запроса:
Access-Control-Request-Method: GET
Access-Control-Request-Headers
— с названиями заголовков через запятую:
Access-Control-Request-Headers: x-requested-with
В ответ сервер должен отправить следующие заголовки:
Access-Control-Allow-Origin
— с названием домена из заголовка запросаOrigin
или символом*
:
$allowed_domains = array(
'http://localhost'
);
if ( isset( $_SERVER['HTTP_ORIGIN'] ) ) {
if ( !in_array($_SERVER['HTTP_ORIGIN'], $allowed_domains) )
exit();
header('Access-Control-Allow-Origin: ' .
$_SERVER['HTTP_ORIGIN']);
}
Access-Control-Allow-Methods
— с разрешенными методами запроса через запятую:
header('Access-Control-Allow-Methods: GET, OPTIONS');
Access-Control-Allow-Headers
— с разрешенными заголовками через запятую:
header('Access-Control-Allow-Headers: ' .
'X-Requested-With, Content-Type');
Access-Control-Max-Age
— задает значение в секундах, в течение которого можно кешировать ответ на запросOPTIONS
без отправки другого запросаOPTIONS
. Нельзя задать значение больше, чем разрешено Web-браузером. Укажем десять минут:
header('Access-Control-Max-Age: 600');
Access-Control-Allow-Credentials
— если опцияcredentials
объекта запроса имеет значение"include"
, то в этом заголовке нужно указать значениеtrue
. Причем в заголовкеAccess-Control-Allow-Origin
обязательно должен быть указан домен из заголовка запросаOrigin
, в противном случае будет ошибка. Пример:
header('Access-Control-Allow-Credentials: true');
Если предварительный запрос выполнен успешно, то Web-браузер отправит обычный запрос, указав заголовок Origin
с названием домена. Сервер должен вернуть заголовок Access-Control-Allow-Origin
. Если опция credentials
объекта запроса имеет значение "include"
, то дополнительно нужно отправить заголовок Access-Control-Allow-Credentials
со значением true
.
Рассмотрим пример выполнения кроссдоменного запроса. Создадим документ с формой http://localhost/test.html (листинг 3.17), который будет обмениваться данными с файлом http://site1/ajax.php (листинг 3.18). Чтобы запрос не был простым, укажем заголовок X-Requested-With
. В качестве ответа получим подтверждение в формате JSON. Дополнительно произведем отправку и получение cookies.
Листинг 3.17. Содержимое файла http://localhost/test.html
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<title>Кроссдоменные запросы</title>
</head>
<body>
<div class="container my-3">
<form action="ajax.php" method="GET" onsubmit="return false;">
<div class="form-group">
<input type="text" class="form-control" id="txt1">
</div>
<button type="button" class="btn btn-primary"
id="btnSend">Отправить методом GET</button>
</form>
</div>
<div class="container my-3">
<div id="div_ajax"></div>
</div>
<script>
async function sendReqGET() {
if ( !window.fetch ) {
window.alert('Ваш браузер не поддерживает Fetch API');
return;
}
let txt1 = document.getElementById('txt1').value;
if (txt1 === '') {
window.alert('Не заполнено поле');
return;
}
let url = 'http://site1/ajax.php?txt1=' + encodeURIComponent(txt1);
document.getElementById('div_ajax').innerHTML = 'Загрузка...';
try {
let response = await fetch(url, {
mode: 'cors',
headers: {'X-Requested-With': 'fetch'},
credentials: 'include'
});
if ( !response.ok ) {
throw new Error('Статус: ' + response.status);
}
let json = await response.json();
const msg = json.txt1 + '<br>' + json.myCookie;
document.getElementById('txt1').value = '';
document.getElementById('div_ajax').innerHTML = msg;
} catch(e) {
document.getElementById('div_ajax').innerHTML = 'Ошибка: ' + e;
}
}
document.getElementById('btnSend').onclick = sendReqGET;
</script>
</body>
</html>
Листинг 3.18. Содержимое файла http://site1/ajax.php
<?php
$allowed_domains = array(
'http://localhost'
);
if ( isset( $_SERVER['HTTP_ORIGIN'] ) ) {
if ( !in_array($_SERVER['HTTP_ORIGIN'], $allowed_domains) )
exit();
header('Access-Control-Allow-Origin: ' .
$_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Credentials: true');
}
if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) {
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: ' .
'X-Requested-With, Content-Type');
header('Content-Type: application/json; charset=utf-8');
header('Content-Length: 0');
exit();
}
// Запрещаем кеширование
header('Expires: Tue, 12 May 2020 01:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
// Указываем MIME-тип и кодировку
header('Content-Type: application/json; charset=utf-8');
if ( isset($_GET['txt1']) ) {
if ( isset($_COOKIE['myCookie']) ) {
$myCookie = $_COOKIE['myCookie'];
}
else $myCookie = '';
setcookie('myCookie', time());
$txt1 = htmlspecialchars($_GET['txt1'], ENT_COMPAT, 'UTF-8');
$arr = array('txt1' => $txt1, 'myCookie' => $myCookie);
echo json_encode($arr);
}
else {
$arr = array('txt1' => 'Данные не получены', 'myCookie' => '');
echo json_encode($arr);
}
Чтобы иметь возможность выполнять кроссдоменные запросы, при создании запроса мы передаем функции fetch()
опцию mode
со значением "cors"
. На самом деле "cors"
является значением по умолчанию, поэтому опцию mode
можно вообще не указывать, но явное лучше неявного. Так как мы указываем заголовок X-Requested-With
, наш запрос не является простым, поэтому сначала Web-браузер отправит предварительный запрос методом OPTIONS
. Чтобы иметь возможность отправлять и принимать cookies указывается опция credentials
со значением "include"
:
let response = await fetch(url, {
mode: 'cors',
headers: {'X-Requested-With': 'fetch'},
credentials: 'include'
});
Если Web-браузер отправит заголовок Origin
, то на сервере будет доступна переменная окружения $_SERVER['HTTP_ORIGIN']
. Мы должны вернуть заголовок Access-Control-Allow-Origin
с тем же значением, что и в заголовке Origin
. Символ *
в данном случае указывать нельзя по двум причинам: во-первых, мы работаем с cookies, во-вторых, злоумышленник может украсть данные сессии. Поэтому, прежде чем отправить заголовок с разрешением, мы проверяем наличие домена в массиве $allowed_domains
. Если домен не найден в массиве разрешенных доменов, то просто завершаем выполнение программы. Чтобы иметь возможность отправлять и принимать cookies указываем заголовок Access-Control-Allow-Credentials
со значением true
.
При отправке Web-браузером предварительного запроса, переменная окружения $_SERVER['REQUEST_METHOD']
будет содержать значение "OPTIONS"
. В этом случае мы отправляем два заголовка: Access-Control-Allow-Methods
(с поддерживаемыми методами) и Access-Control-Allow-Headers
(с поддерживаемыми заголовками). Отправляем еще заголовки Content-Type
и Content-Length
со значением 0
и завершаем выполнение программы.
Если запрос обычный, то запрещаем кеширование, а также указываем MIME-тип и кодировку данных. Если Web-браузер отправил cookies, то переменная окружения $_COOKIE['myCookie']
будет определена. Получаем ее значение и отправляем его в составе JSON-запроса. Устанавливаем cookies myCookie
с помощью функции setcookie()
, указывая в качестве значения число секунд, прошедшее с 1 января 1970 г., которое возвращает функция time()
. Если не указать опцию credentials
со значением "include"
, то работать с cookies мы не сможем. Далее отправляем ответ в формате JSON и получаем его в Web-браузере как обычно.
XMLHttpRequest
также использует правила политики CORS при кроссдоменных запросах.Помощь сайту
ЮMoney (Yandex-деньги): 410011140483022
ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов