Кросс-доменный скриптинг - общее название для случая, когда страницы с одного домена производят запрос на другой.
Он бывает полезен как для связи сервисов от различных поставщиков, так и для общения разнородных ресурсов в рамках одного общего домена второго уровня.
В зависимости от того, одинаковый домен второго уровня или разный - применяются разные способы организации кросс-доменных запросов.
Полностью cross-domain
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 и данных запроса серверный документ может и должен, перед тем, как делать реальный запрос на сервер, фильтровать, что и кому разрешено.
Так назовем коммуникацию, когда 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:
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
возвращается обратно. Возникающие при этом исключения игнорируются
Основной документ, http://tmp.x/main.html
03 | < script type = "text/javascript" > |
05 | document.domain="tmp.x"; |
07 | function gotTime(result) { document.getElementById('time').innerHTML = result } |
14 | < iframe src = "http://www.tmp.x/iframe.html" ></ iframe > |
04 | < script type = "text/javascript" src = "xmlhttp.js" ></ script > |
05 | < script type = "text/javascript" > |
07 | // сделать запрос на адрес с того же домена, что и iframe, |
08 | // указать каллбэк gotResult |
09 | getUrl("http://www.tmp.x/time.php", gotResult); |
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) |
21 | // вернуть домен обратно на www.tmp.x, это необходимо IE, |
22 | // чтобы сделать новый запрос на www.tmp.x |
24 | // для IE, в остальных браузерах ошибка... |
25 | document.domain = oldDomain; |
26 | } catch(e) { /* ... но там это не нужно */ } |
28 | // запускаем новый запрос.. |
Комментариев нет:
Отправить комментарий