Javascript.RU

Обмен данными для документов с разных доменов

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/xhr-crossdomain.

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

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

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

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.

Для транспорта необходимо создать два ифрейма. Один - клиентский, на том же домене, что и основное окно, и в нем - серверный, на домене сервера.

  1. На клиенте - создать ифрейм, в который загрузить специальный документ с сервера на другом домене
  2. Определить протокол передачи информации через идентификаторы фрагмента
  3. Сообщить обоим документам URL друг друга, так чтобы они могли правильно устанавливать идентификаторы фрагмента (чтобы установить адрес на другом домене, браузеру нужен полный URL)
  4. Использовать таймер javascript для обнаружения изменений фрагментов

Чтобы послать XMLHTTPRequest на другой домен:

  1. Создать javascript-объект, который реализует интерфейс XMLHTTPRequest (фасад)
  2. Использовать этот объект вместо реального объекта XMLHTTPRequest
  3. Для метода send() фасада сериализовать заголовки, метод, URL и данные запроса
  4. Браузер накладывает ограничение на размер URL документа, поэтому клиентский документ разбивает сериализованные данные в набор идентификаторов фрагментов подходящего размера
  5. Клиентский документ по очереди отсылает идентификаторы фрагментов на серверный документ (в ифрейм). Серверный документ подтверждает получение каждого
    идентификатора, и так - до тех пор, пока все данные не будут переданы.
  6. Серверный документ собирает из идентификаторов фрагментов исходные данные и преобразует их обратно в объект, а затем использует настоящий XMLHTTPRequest (на
    серверном домене), чтобы сделать запрос к серверу
  7. Затем серверный документ сериализует ответ на XMLHTTPRequest, и точно так же передает клиентскому документу через идентификаторы фрагмента
  8. Клиентский документ распаковывает ответ и ставит соответствующие значения в фасаде
  • 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

  1. Создать IFrame c другим поддоменом
  2. Сделать запрос с этого IFrame
  3. Установить document.domain такой же как у фрейма, куда надо отправить информацию и вызвать нужные функции
  4. Для 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..


Автор: Ивашка, дата: 28 марта, 2008 - 23:46
#permalink

MS анонсировала для восьмого IE объект XDomainRequest
Я так полагаю, что это у них серьезно и стоит его принять во внимание.


Автор: Илья Кантор, дата: 30 марта, 2008 - 02:20
#permalink

Да, а другие браузеры анонсировали поддержку кросс-доменных запросов в соответствии со стандартами. MS выпендрилась опять


Автор: mayhem (не зарегистрирован), дата: 13 ноября, 2008 - 18:34
#permalink

вроде как еще window.name есть


Автор: Джо (не зарегистрирован), дата: 26 декабря, 2008 - 00:24
#permalink

Все замечательно, но где можно посмотреть небольшой пример-модель взаимодействия по варианту XhrIframeProxy ?


Автор: Никита) (не зарегистрирован), дата: 26 января, 2009 - 18:58
#permalink

даже не знал что между 2 доменами можно устраивать обмен данными


Автор: Гость (не зарегистрирован), дата: 18 марта, 2009 - 16:01
#permalink

"Наиболее известный транспорт, позволяет такие запросы - это SCRIPT, т.к может подгружать яваскрипт с любого домена."
Работает только в IE и то с окошком про безопасность.

Про JSON забыли.


Автор: TT (не зарегистрирован), дата: 12 января, 2010 - 17:49
#permalink

Может ли в этом деле помочь букмарклет?

Нужно открыть произвольную текущую страницу во фрейме нового фреймсета, а в другом его фрейме - наш JS-контрол, который мог бы перехватывать события страницы.

1-й вопрос
Допустим, при запуске из закладок букмарклет запрашивает фреймсет с нужными фреймами, ПОСЛЕ ЧЕГО во фрейм с чужим документом добавляет свой скрипт, который через сёрвер моего домена сообщает о событиях и принимает команды. ПОСЛЕ ЧЕГО - возможно ли?

2-й вопрос
Вопрос от незнания JS (равно как и первый), но ответьте: может ли букмарклет создать объект, доступный каждому фрейму и при этом не принадлежащий ни одному домену? Например, он сам может не умирать после вышеперечисленных действий и реализовать в себе такой объект? Это позволило бы установить канал связи, минуя сетевой обмен.

Я не ищу хакерского решения - мне нужна стабильность во времени и в пространстве существующих браузеров.
Я рассуждаю так: пользователь, устанавливая букмарклет, предупреждается, о возможной угрозе. Запуск механизма он инициирует тоже сам, сознательно. Как-бы Сабмит жмёт. Скрипт, который и так подсаживается в целевую страницу уже может ею манипулировать, так что осознанность действий юзера уже фактически одобрена политикой безопасности. Так в чём особая проблема разрешить взаимодействие с фреймом из домена, с которым - с доменом - и так уже установлена через скрипт в целевой странице связь?


Автор: KOLANICH, дата: 25 января, 2010 - 17:52
#permalink

нельзя не упомянуть специфический способ для GreaseMonkey скриптов

GM_xmlhttpRequest({
  method: "POST",
  url: serveraddr,
  data: params,
  headers: {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  onload:function(response){....});

Автор: al (не зарегистрирован), дата: 1 марта, 2010 - 09:54
#permalink

Максимум два соединения - это параллельные независимые соединения между различными доменами? Для одно домена их хоть десяток наделать можно, правда последовательных, будут висеть в ожидании, пока не закроется предыдущее.
Кроссдоменность с document.domain получается неработает без фреймов? Если на странице создать первое XHR соединение с site.ru, затем document.domain="forum.site.ru" и попробовать после этого создать второй XHR с запросом на forum.site.ru, то получаем запрет доступа.


Автор: Росток (не зарегистрирован), дата: 14 октября, 2010 - 02:34
#permalink

привет. скажите пожалуйста сколько данных (объем/кол-во) можно передать используя метод SCRIPT?


Автор: mycoding, дата: 15 мая, 2011 - 09:52
#permalink

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

Еще можно использовать actipnscript, но лучше не заворачиваться с ним.
Всё остальное это не кросс-браузерно и никто его сделать не сможет.

Также следует упомянуть слово JSONP.


Автор: Neznayka (не зарегистрирован), дата: 6 июля, 2011 - 22:55
#permalink

Не получается получить доступ к содержимому ифрейма в который загружен документ с той же локальной папки что и основной документ. Пишут броузеры что не совпадает протокол, порт, или домен. Тоже самое с сервера работает нормально.
Устанавливал document.domain и contentDocument.domain в localhost - не помогает.
Можно каким то другим способом положить html код из одного локального документа в JS переменную другого? Спасибо.


Автор: Deff, дата: 1 января, 2013 - 20:22
#permalink

Идеи кроссдоменного обмена через анонимайзер + фрейм: http://javascript.ru/forum/project/34378-krossdomenyjj-obmen-cherez-anon...


Автор: yloboda, дата: 2 апреля, 2014 - 11:45
#permalink

А где скрипт xmlhttp.js? У меня ошибка в iframe сервер не найден..


Автор: KellieMiles (не зарегистрирован), дата: 19 апреля, 2022 - 06:10
#permalink

Это здорово, спасибо всем, что поделились, надеюсь, я смогу чем-то помочь


 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum