Обмен данными для документов с разных доменов
Кросс-доменный скриптинг - общее название для случая, когда страницы с одного домена производят запрос на другой.
Он бывает полезен как для связи сервисов от различных поставщиков, так и для общения разнородных ресурсов в рамках одного общего домена второго уровня.
В зависимости от того, одинаковый домен второго уровня или разный - применяются разные способы организации кросс-доменных запросов.
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="..."> может подгружать яваскрипт с любого домена.
- Запросы на любые домены
- Только GET
- Необходима обертка в Javascript
- Сложно отслеживать ошибки, лаги и т.п.
Если допустимо использование Flash - в нем есть свои средства кросс-доменных соединений (crossdomain.xml), и можно удобно передавать данные из flash в javascript.
Передача данных из javascript во flash несколько сложнее, но тоже осуществима.
Для использования такого транспорта, очевидно, необходим включенный flash.
- Flash предоставляет универсальные средства обмена данными
- Нужен рабочий Flash и "мост" из javascript во 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.
Для транспорта необходимо создать два ифрейма. Один - клиентский, на том же домене, что и основное окно, и в нем - серверный, на домене сервера.
- На клиенте - создать ифрейм, в который загрузить специальный документ с сервера на другом домене
- Определить протокол передачи информации через идентификаторы фрагмента
- Сообщить обоим документам URL друг друга, так чтобы они могли правильно устанавливать идентификаторы фрагмента (чтобы установить адрес на другом домене, браузеру нужен полный URL)
- Использовать таймер javascript для обнаружения изменений фрагментов
Чтобы послать XMLHTTPRequest на другой домен:
- Создать javascript-объект, который реализует интерфейс XMLHTTPRequest (фасад)
- Использовать этот объект вместо реального объекта XMLHTTPRequest
- Для метода send() фасада сериализовать заголовки, метод, URL и данные запроса
- Браузер накладывает ограничение на размер URL документа, поэтому клиентский документ разбивает сериализованные данные в набор идентификаторов фрагментов подходящего размера
- Клиентский документ по очереди отсылает идентификаторы фрагментов на серверный документ (в ифрейм). Серверный документ подтверждает получение каждого
идентификатора, и так - до тех пор, пока все данные не будут переданы.
- Серверный документ собирает из идентификаторов фрагментов исходные данные и преобразует их обратно в объект, а затем использует настоящий XMLHTTPRequest (на
серверном домене), чтобы сделать запрос к серверу
- Затем серверный документ сериализует ответ на XMLHTTPRequest, и точно так же передает клиентскому документу через идентификаторы фрагмента
- Клиентский документ распаковывает ответ и ставит соответствующие значения в фасаде
- 100% javascript
- Легко вставляется в код, который использует XMLHTTPRequest
- Техника использует ифреймы, поэтому требует больше памяти, чем родной XMLHTTPRequest
- Трафик - в оба служебных ифрейма нужно загрузить изначальные скрипты и документы. В дальнейшем они могут быть кешированы
- Установка URL'ов в IFrame'ы дает кликающие звуки в Internet Explorer. Это можно исправить при помощи ActiveX (описано в разделе транспорта Iframe)
Основной реальный минус, по мнению автора - это конкретный хак. С другой стороны, более пристойные предложения, хоть и внесены в 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 можно присваивать:
- На текущий домен
- На наддомен, но не вида org.ru (т.е job.site.com.ua можно поменять на site.com.ua, но не на com.ua)
- В Firefox/Opera, после изменения домена на более короткий - вернуть домен обратно нельзя, в IE - можно
Так что в случае документов с разных сайтов на одном наддомене, можно присвоить свойству 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
- Создать IFrame c другим поддоменом
- Сделать запрос с этого IFrame
- Установить document.domain такой же как у фрейма, куда надо отправить информацию и вызвать нужные функции
- Для IE - вернуть document.domain обратно перед следующим запросом
В этом примере используется как общение ифреймов с общим наддоменом, так и XMLHTTPRequest. Основной документ http://tmp.x/main.html регулярно получает данные с http://www.tmp.x/time.php, используя для этого промежуточный ифрейм http://www.tmp.x/iframe.html.
Общая логика:
- Изначально iframe берется с домена, на который надо делать XHR-запросы
- Затем для общения с родителем
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..
|
MS анонсировала для восьмого IE объект XDomainRequest
Я так полагаю, что это у них серьезно и стоит его принять во внимание.
Да, а другие браузеры анонсировали поддержку кросс-доменных запросов в соответствии со стандартами. MS выпендрилась опять
вроде как еще window.name есть
Все замечательно, но где можно посмотреть небольшой пример-модель взаимодействия по варианту XhrIframeProxy ?
даже не знал что между 2 доменами можно устраивать обмен данными
"Наиболее известный транспорт, позволяет такие запросы - это SCRIPT, т.к может подгружать яваскрипт с любого домена."
Работает только в IE и то с окошком про безопасность.
Про JSON забыли.
Может ли в этом деле помочь букмарклет?
Нужно открыть произвольную текущую страницу во фрейме нового фреймсета, а в другом его фрейме - наш JS-контрол, который мог бы перехватывать события страницы.
1-й вопрос
Допустим, при запуске из закладок букмарклет запрашивает фреймсет с нужными фреймами, ПОСЛЕ ЧЕГО во фрейм с чужим документом добавляет свой скрипт, который через сёрвер моего домена сообщает о событиях и принимает команды. ПОСЛЕ ЧЕГО - возможно ли?
2-й вопрос
Вопрос от незнания JS (равно как и первый), но ответьте: может ли букмарклет создать объект, доступный каждому фрейму и при этом не принадлежащий ни одному домену? Например, он сам может не умирать после вышеперечисленных действий и реализовать в себе такой объект? Это позволило бы установить канал связи, минуя сетевой обмен.
Я не ищу хакерского решения - мне нужна стабильность во времени и в пространстве существующих браузеров.
Я рассуждаю так: пользователь, устанавливая букмарклет, предупреждается, о возможной угрозе. Запуск механизма он инициирует тоже сам, сознательно. Как-бы Сабмит жмёт. Скрипт, который и так подсаживается в целевую страницу уже может ею манипулировать, так что осознанность действий юзера уже фактически одобрена политикой безопасности. Так в чём особая проблема разрешить взаимодействие с фреймом из домена, с которым - с доменом - и так уже установлена через скрипт в целевой странице связь?
нельзя не упомянуть специфический способ для GreaseMonkey скриптов
Максимум два соединения - это параллельные независимые соединения между различными доменами? Для одно домена их хоть десяток наделать можно, правда последовательных, будут висеть в ожидании, пока не закроется предыдущее.
Кроссдоменность с document.domain получается неработает без фреймов? Если на странице создать первое XHR соединение с site.ru, затем document.domain="forum.site.ru" и попробовать после этого создать второй XHR с запросом на forum.site.ru, то получаем запрет доступа.
привет. скажите пожалуйста сколько данных (объем/кол-во) можно передать используя метод SCRIPT?
Думаю, что следуюет указать, что Script транспорт - это единственный реально используемый метод для кросс-доменных запросов.
Еще можно использовать actipnscript, но лучше не заворачиваться с ним.
Всё остальное это не кросс-браузерно и никто его сделать не сможет.
Также следует упомянуть слово JSONP.
Не получается получить доступ к содержимому ифрейма в который загружен документ с той же локальной папки что и основной документ. Пишут броузеры что не совпадает протокол, порт, или домен. Тоже самое с сервера работает нормально.
Устанавливал document.domain и contentDocument.domain в localhost - не помогает.
Можно каким то другим способом положить html код из одного локального документа в JS переменную другого? Спасибо.
Идеи кроссдоменного обмена через анонимайзер + фрейм: http://javascript.ru/forum/project/34378-krossdomenyjj-obmen-cherez-anon...
А где скрипт xmlhttp.js? У меня ошибка в iframe сервер не найден..
Это здорово, спасибо всем, что поделились, надеюсь, я смогу чем-то помочь