Как разработчик JavaScript, вы, вероятно, широко используете Ajax для обмена данными с сервером или обновления веб-страницы без обновления. Хотя отправка запроса Ajax на ваш сервер является довольно простым запросом, обмен данными с сервером в другом домене — это совсем другая история. Давайте попробуем это!
Давайте запустим следующее с http://www.mysite.com
(или 127.0.0.1
/ localhost
) в Chrome 32.
request = new XMLHttpRequest; request.open('GET', 'http://myothersite.com/', true); request.send();
Вы получите ошибку. XMLHttpRequest
не может загрузить http://myothersite.com/
. В запрошенном ресурсе отсутствует заголовок 'Access-Control-Allow-Origin'
. Origin http://www.mysite.com
поэтому не допускается доступ.
Почему это происходит? Разве мы не сделали все правильно?
Политика одного происхождения
Политика того же источника разрешает сценариям, запущенным в браузере, отправлять запросы только на страницы в одном домене. Это означает, что запросы должны иметь одинаковую схему URI, имя хоста и номер порта. Этот пост в Mozilla Developer Network четко определяет определение источника и когда запросы приводят к сбою. Если вы отправляете запрос с http://www.mysite.com/
, следующие типы запросов приводят к сбою.
-
https://www.mysite.com/
— Другой протокол (или схема URI). -
http://www.mysite.com:8080/myUrl
— Другой порт (поскольку HTTP-запросы по умолчанию выполняются на порту80
). -
http://www.myothersite.com/
— Другой домен. -
http://mysite.com/
— Рассматривается как другой домен, поскольку требует точного соответствия (обратите внимание, что нетwww.
).
Изменение происхождения
Иногда одна и та же политика происхождения может блокировать запросы между поддоменами в одном домене. Самый простой способ решить эту проблему — установить document.domain
из JavaScript. Например:
document.domain = 'mysite.com';
Обратите внимание, что номер порта хранится отдельно. Чтобы заставить один домен взаимодействовать с другим через другой порт (как в случае с приложениями чата), потребуется нечто иное. Даже установка document.domain = document.domain
, которая перезаписывает номер порта на null
, не поможет в этом.
Использование веб-прокси
Хотя указание document.domain
помогает вам связываться с поддоменами вашего собственного веб-сайта, что бы вы сделали, если бы вам понадобился вообще доступ к данным из другого домена? Интересный, но простой для понимания подход заключается в использовании веб-прокси на вашем собственном сервере.
Вместо того, чтобы отправлять запрос напрямую с вашего домена ( http://www.mysite.com/
) на новый домен ( http://www.myothersite.com/
), вы вместо этого отправляете запрос на свой собственный сервер ( http://www.mysite.com/connect/
), который, в свою очередь, отправляет запрос на новый домен ( http://www.myothersite.com/
). Похоже, что в браузере вы обмениваетесь данными с вашим собственным сервером. В действительности, в фоновом режиме вы получили доступ к данным в новом домене с вашего сервера. Блок-схема, объясняющая процесс, показана ниже.
Источник: Yahoo Developers
Использование JSONP
Другим способом реализации межбраузерных запросов является использование JSONP или «JSON with padding». JSONP использует тот факт, что теги <script>
не подчиняются политике одного и того же происхождения. Например, вы можете включить библиотеку, такую как jQuery, на свою страницу, даже если она размещена в CDN Google.
Запросы JSONP выполняются путем динамического запроса <script>
. Интересно то, что ответ JSON заключен в вызов функции. Делая запрос, вы указываете имя функции в качестве функции обратного вызова. Когда сервер отвечает, функция обратного вызова (которая должна существовать на вашей странице) выполняется с данными, возвращаемыми с сервера.
Например, типичный ответ JSON может выглядеть следующим образом:
{ "id": "123", "name": "Captain Jack Sparrow" }
Тот же ответ можно эмулировать в теге скрипта с помощью функции обратного вызова myFunction
как показано ниже.
<script src="http://www.myothersite.com/get_data?callback=myFunction"></script>
Браузер обычно загружает данные с указанного URL и оценивает их как JavaScript. Ответ может выглядеть так:
myFunction({"id": "123", "name": "Captain Jack Sparrow"});
Затем будет вызвана myothersite.com
myFunction
, позволяющая вашей странице обрабатывать JSON, возвращенный с myothersite.com
.
Проблемы безопасности — подделка межсайтовых запросов
Поскольку <script>
не соответствует политике одного и того же источника, злоумышленник может получить конфиденциальные данные, используя один и тот же URL-адрес. Используя приведенный выше пример, вредоносная страница может загрузить те же данные JSON и выполнить с ними какое-то недоброе действие. Это известно как атака подделки межсайтовых запросов (CSRF). Некоторые меры противодействия CSRF-атакам включают использование токенов или файлов cookie для проверки и ограничение срока службы таких токенов.
Распределение ресурсов между доменами
Хотя JSONP можно использовать для выполнения большинства задач относительно легко, есть несколько недостатков. Вы можете отправлять только HTTP-запросы GET, используя JSONP. Это исключает любую возможность выполнения операций CRUD чисто с использованием JSONP. Хотя эта проблема безопасности устраняется с помощью прокси-метода, есть еще один интересный метод, который поможет нам взаимодействовать с RESTful API, не перепрыгивая через обручи.
Распределение ресурсов между доменами, или CORS, работает путем изменения заголовков HTTP в ваших запросах для доступа к ресурсам в другом домене. В IE8 + разрешены простые запросы CORS с использованием XDomainRequest
(вместо XMLHttpRequest
). Простой пример показан ниже.
request = new XDomainRequest(); request.open(method, url); request.onload = function() { callback(req.responseText); }; request.send(data);
Мы говорили о «простых» запросах. Не столь простые запросы относятся к предварительно проверенным запросам, которые сначала отправляют HTTP-запрос в другой домен, чтобы определить, безопасно ли выполнять это действие. Подробный пример предварительного запроса и обмена информацией между сервером и клиентом объясняется в этом сообщении MDN о CORS .
Вывод
Хотя CORS выглядит как будущее для интерфейсного программирования, вы все равно должны использовать его с осторожностью, поскольку нет поддержки очень старых браузеров (IE7 и более ранних версий). Поддержка CORS — это небольшая проблема, но вы обязательно должны попробовать ее! Для дальнейшего чтения я предлагаю вам просмотреть подробный пост MDN по CORS .