Translate

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

Кросс-доменный скриптинг - общее название для случая, когда страницы с одного домена производят запрос на другой.
Он бывает полезен как для связи сервисов от различных поставщиков, так и для общения разнородных ресурсов в рамках одного общего домена второго уровня.
В зависимости от того, одинаковый домен второго уровня или разный - применяются разные способы организации кросс-доменных запросов.
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возвращается обратно. Возникающие при этом исключения игнорируются
01<html>
02<head>
03    <script type="text/javascript">
04        <!-- обрезаем домен текущей страницы до базового -->
05        document.domain="tmp.x";
06        <!-- эту функцию будет вызывать ифрейм с www.tmp.x -->
07        function gotTime(result) { document.getElementById('time').innerHTML = result }
08    </script>
09</head>
10<body>
11    Счетчик
12    <div id="time"></div>
13    <!-- iframe с другого домена -->
14    <iframe src="http://www.tmp.x/iframe.html"></iframe>
15</body>
16</html>
01<html>
02<head>
03    <!-- подгружаем функцию getUrl (делает XMLHTTPRequest) -->
04    <script type="text/javascript" src="xmlhttp.js"></script>
05    <script type="text/javascript">
06        function getTime(){
07            // сделать запрос на адрес с того же домена, что и iframe,
08            // указать каллбэк gotResult
09            getUrl("http://www.tmp.x/time.php", gotResult);
10        }
11        getTime();
12
13        // каллбэк для обработки результата запроса
14        function gotResut(status, headers, result) {
15            // для общения с родительским документом нужно поменять domain на tmp.x
16            var oldDomain = document.domain
17            document.domain = "tmp.x"
18            // и вызвать родителя, document.domain поставлен одинаковый там и тут
19            window.parent.gotTime(result)
20  
21            // вернуть домен обратно на www.tmp.x, это необходимо IE,
22            // чтобы сделать новый запрос на www.tmp.x
23            try {
24                // для IE, в остальных браузерах ошибка...
25                document.domain = oldDomain;
26            } catch(e) {  /* ... но там это не нужно */ }
27
28            // запускаем новый запрос..
29            getTime()
30        }
31    </script>
32</head>
33<body></body>
34</html>
В коде iframe.html домен меняется туда-обратно, потому что нужно делать запросы на www.tmp.x, а ответ сообщать документу с tmp.x.

http://javascript.ru/ajax/cross-domain-scripting

Комментариев нет:

Отправить комментарий

Постоянные читатели