Кросс-доменный скриптинг - общее название для случая, когда страницы с одного домена производят запрос на другой.
Он бывает полезен как для связи сервисов от различных поставщиков, так и для общения разнородных ресурсов в рамках одного общего домена второго уровня.
В зависимости от того, одинаковый домен второго уровня или разный - применяются разные способы организации кросс-доменных запросов.
site1.net делает запрос на site2.net - в этом случае домены совершенно разные.
Документы, полученные в одном фрейме с site1.net не смогут обращаться к другому через JS, если он с site2.net.
Вообще, документы с разных доменов, протоколов или с разных портов (кроме IE) одного домена не могут общаться друг с другом (согласно same origin policy), и нельзя посылать XMLHTTPRequest на домен, отличный от текущего.
Самое простое решение - это проксирование запроса сервером. То есть, site1.net делает специальный запрос, например, на особенный URL
типа http://site1.net/proxy/site2.net/test.html, и сервер site1.net проксирует его на http://site2.net/test.html.
Если оба сайта работают на одном движке, то можно обойтись даже без проксирования, просто соответствующим образом закодировать обработку запросов
http://site1.net/proxy/*.
Из минусов такого подхода - лишняя нагрузка на сервер и дополнительные сетевые задержки при проксировании. Особенность - необходимость соответствующей серверной инфраструктуры.
Наиболее известный транспорт, позволяет такие запросы - это SCRIPT, т.к <script src="..."> может подгружать яваскрипт с любого домена.
Если допустимо использование Flash - в нем есть свои средства кросс-доменных соединений (crossdomain.xml), и можно удобно передавать данные из flash в javascript.
Передача данных из javascript во flash несколько сложнее, но тоже осуществима.
Для использования такого транспорта, очевидно, необходим включенный flash.
Из смеси XMLHTTPRequest и Iframe получается оригинальный хак, называемый XhrIframeProxy. Он позволяет делать кросс-доменные запросы XmlHttpRequest, и успешно протестирован в Internet Explorer 6/7, Firefox 1.5+, Safari 2.0.3 и Opera 9.
Транспорт основан на том, что ифреймы, даже находясь на разных доменах, могут общаться друг с другом при помощи изменения идентификаторов фрагментов адресов.
Идентификатор фрагмента - это то, что идет в URL после решетки: http://site.com/path/to/file.html#fragment.
Документ, загруженный в IFrame, может менять идентификатор фрагмента родительского документа (т.е документа, содержащего iframe). Изменение фрагмента не приводит
к перезагрузке страницы. И, аналогично, родительский документ может менять идентификатор фрагмента в ифрейме.
Путем последовательных изменений #фрагмента образуется поток данных, который может передаваться в обе стороны. Т.к идентификатор фрагмента - текст, то все данные для передачи приходится (де)сериализовать, т.е превращать в JSON.
Для транспорта необходимо создать два ифрейма. Один - клиентский, на том же домене, что и основное окно, и в нем - серверный, на домене сервера.
Чтобы послать XMLHTTPRequest на другой домен:
Основной реальный минус, по мнению автора - это конкретный хак. С другой стороны, более пристойные предложения, хоть и внесены в W3C, но еще долго будут выходить
в мейнстрим. Несмотря на неказистый внешний вид: ифреймы, таймеры, сериализация - этот транспорт работает и кросс-браузерный.
XhrIframeProxy - не дает открытый доступ к любому сервису с XMLHTTPRequest API. Для того, чтобы этот транспорт работал, на сервере должен быть серверный документ, для загрузки в серверный ифрейм. Клиент никак не может повлиять на этот документ, т.к он с другого домена. Клиент обязан сообщить серверному документу свой полный URL, т.к он используется для установки идентификаторов фрагмента.
На основании клиентского URL и данных запроса серверный документ может и должен, перед тем, как делать реальный запрос на сервер, фильтровать, что и кому разрешено.
XhrIframeProxy реализован в dojo toolkit, и описан в dojo book: Cross Domain XMLHttpRequest using an IFrame Proxy. На момент написания этих слов, документация в dojo book устарела, лучше смотреть реализацию в SVN.
Так назовем коммуникацию, когда domain1.site.net делает запрос на domain2.site.net или site.net. То есть, когда есть общий наддомен, в данном случае site.net.
Основной вопрос - зачем такое вообще может понадобиться?
Первый сценарий - наш сайт domain.site.net является частью некой системы, в которой адрес news.site.net предоставляет ленту новостей, goods.site.net - товары,
и т.п., так что мы, имея такой домен, можем с удобством пользоваться этими вебсервисами.
Второй сценарий - оба таких сайта находятся под нашим контролем… Скажем, www.site.net и site.net формально на разных доменах, но могут одинаково обрабатываться сервером.
В таком случае все необходимые запросы можно сделать и на текущий домен - они так и так попадут к нам на сервер.
Но здесь появляется второе применение кросс-доменного скриптинга. А именно, обход ограничения HTTP 1.1 на соединения: не более двух одновременных запросов к серверу(домену/порту/протоколу).
Однажды, мне пришлось писать AJAX-компонент, который делает запросы к нескольким вебсервисам, причем время отклика может варьироваться (в зависимости от запроса) между 1-20 сек. При этом одно соединение было постоянно занято подгрузкой бесконечного ифрейма с сервера, через которое поступают обновления (push данных со стороны сервера в виде <script>-тагов). Оставался один канал на все про все - явно недостаточно для асинхронных запросов.
Также возможны случаи, когда нужно поддерживать несколько push-каналов, например, дополнительно открыто окно мини-чата, который реализован отдельно от общих обновлений.
Кросс-доменный скриптинг позволяет обойти лимит путем использования нескольких доменов. Бесконечный ифрейм подгружаем с updates.site.net, чат работает на chat.site.net, и свободны основные 2 канала для запросов с site.net.
Другой, пожалуй более распространенный пример использования - когда на основном сервере site.net крутится веб-сервер типа Apache, который не очень любит долговременные соединения, а на сервере chat.site.net крутится демон чата. Получается, что документы с разных серверов могут полноценно взаимодействовать на клиенте.
Как известно, обычно javascript из одного фрейма может как-то вызывать другой фрейм, только если они с одного домена. Но домен хранится в специальном свойстве document.domain, которое можно менять. Так что если два фрейма имеют один document.domain , то они могут делать друг с другом что угодно.
Конечно, есть ограничения безопасности - document.domain можно присваивать:
Так что в случае документов с разных сайтов на одном наддомене, можно присвоить свойству document.domain обоих документов этот общий домен, и тогда javascript-общение между ними возможно.
Обращаю внимание, что даже если документ и так с нужного домена site.com, то все равно нужно поставить document.domain:
// в документе с site.com надо поставить домен, хотя бы и так document.domain = document.domain
После смены домена с domain.site.com на site.com, можно не только вызывать javascript для других документов с site.com, но и делать XMLHTTPRequest-запросы на site.com.
Что делать, если хочется делать запросы и на более короткий домен site.com и на исходный domain.site.com?
Перед следующим запросом в IE нужно вернуть document.domain обратно. В Opera/FF это невозможно (домен может быть установлен только в текущий или в домен высшего уровня), но в Opera 9/FF 1.5+ ситуация поправлена тем, что повторные запросы с исходного домена разрешаются.
Итак, алгоритм для IE/FF 1.5+/Opera 9
В этом примере используется как общение ифреймов с общим наддоменом, так и XMLHTTPRequest. Основной документ http://tmp.x/main.html регулярно получает данные с http://www.tmp.x/time.php, используя для этого промежуточный ифрейм http://www.tmp.x/iframe.html.
Общая логика:
document.domain ставится равный общему с родителем наддомену, для общения с исходным доменом document.domain возвращается обратно. Возникающие при этом исключения игнорируются
<html>
<head>
<script type="text/javascript">
<!-- обрезаем домен текущей страницы до базового -->
document.domain="tmp.x";
<!-- эту функцию будет вызывать ифрейм с www.tmp.x -->
function gotTime(result) { document.getElementById('time').innerHTML = result }
</script>
</head>
<body>
Счетчик
<div id="time"></div>
<!-- iframe с другого домена -->
<iframe src="http://www.tmp.x/iframe.html"></iframe>
</body>
</html>
<html>
<head>
<!-- подгружаем функцию getUrl (делает XMLHTTPRequest) -->
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript">
function getTime(){
// сделать запрос на адрес с того же домена, что и iframe,
// указать каллбэк gotResult
getUrl("http://www.tmp.x/time.php", gotResult);
}
getTime();
// каллбэк для обработки результата запроса
function gotResut(status, headers, result) {
// для общения с родительским документом нужно поменять domain на tmp.x
var oldDomain = document.domain
document.domain = "tmp.x"
// и вызвать родителя, document.domain поставлен одинаковый там и тут
window.parent.gotTime(result)
// вернуть домен обратно на www.tmp.x, это необходимо IE,
// чтобы сделать новый запрос на www.tmp.x
try {
// для IE, в остальных браузерах ошибка...
document.domain = oldDomain;
} catch(e) { /* ... но там это не нужно */ }
// запускаем новый запрос..
getTime()
}
</script>
</head>
<body></body>
</html>
В коде iframe.html домен меняется туда-обратно, потому что нужно делать запросы на www.tmp.x, а ответ сообщать документу с tmp.x.
О новых способах кросс-доменного общения читайте в продолжении этой статьи: Обмен данными между доменами. Часть 2..