Pull to refresh

Кросс-доменные запросы в Opera UserJS

JavaScript *
В отличие от расширения Greasemonkey в мозилле, Опера не предоставляет аналога функции GM_xmlhttpRequest для кросс-доменных запросов (XDR). Это, понятное дело, сильно ограничивает возможности и сферу применения UserJS. Используя XDR, например, можно реализовать Last.fm-скробблер для различных онлайн-проигрывателей музыки (типа vkontakte.ru или MySpace).

Однако кросс-доменные запросы можно заставить работать в Опере с помощью трюков с iframe'ами и window.name транспортом. Под катом я покажу как это сделать и предъявлю простую библиотеку, реализующую все колдовство.

Вообще говоря, для кросс-доменных запросов используются следующие техники:
  • Проксирование запросов. Если мы владеем сервером, страницы которого хотят обращаться к удаленному веб-сервису, можно реализовать скрипт на нашем сервере, проксирующий запросы к нему. Таким образом, на стороне клиента запросы будут выглядеть как обращение к своему домену, а на самом деле возвращаться будут результаты выполнения запроса на удаленном сервере.
    К минусам этого подхода, помимо необходимости контроля за сервером на запрашивающем домене, можно отнести нагрузку на входящий канал нашего сервера и возможность бана нашего прокси по IP. Кроме того, пользовательские данные проходят через наш сервер, поэтому конечный пользователь должен нам доверять.
  • Запросы через Flash. В случае, когда мы владеем сервером, предоставляющим веб-сервис, можно разместить на нем XML-файл политики, разрешающий их XDR с некоторых доменов. Минусы: требуется контроль за веб-сервисом, требуется Flash.
  • Использование разного рода заменителей асинхронных запросов, например динамическая генерация элементов script. Необходим контроль за веб-сервисом. Кроме того, требуется более-менее серьезное изменение его кода для отдачи корректного яваскрипта.
  • Использование Cross-Origin Resource Sharing. Опять же, требуется контроль над веб-сервисом, да и впридачу эта спецификация, насколько я знаю, еще нигде не поддерживается.


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

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

Суть метода


Казалось бы, все просто: запрос производится из iframe'а с домена вебсервиса, а главная страница с фреймом общается при помощи window.name транспорта. Вспомним, что свойство window.name, будучи единожды установлено, сохраняет свое значение при путешествиях окна через границу домена.

Теперь подробнее. Итак, предположим, мы пишем UserJS для домена domain.ru, который хочет сделать асинхронный запрос к веб-сервису на домене ws.org. Запросы к веб-сервису будет посылать UserJS, запущенный в iframe'е для документа с домена ws.org (допустим, ws.org/dummy.gif: какой конкретно URL — не важно). А для передачи аргументов запроса необходимо заранее, до того как откроется страничка с удаленного домена, установить window.name у фрейма в строку, содержащую эти параметры. Таким образом, UserJS на ws.org получит аргументы запроса из своего window.name и оправит необходимый XMLHttpRequest к ws.org. Ему уже можно: запрос в пределах домена ws.org. Затем результат выполнения засовывается снова в window.name фрейма, и фрейм редиректится на любую страничку на domain.ru (например, domain.ru/dummy2.gif), где снова выполняется UserJS, который запускает обработчик результата запроса в главном окне.
Разумеется, надо выбирать документы dummy.gif и dummy2.gif, у которых установлены правильные кеширующие заголовки, потому что каждый запрос проходит через них.

На схеме это выглядит примерно так:
uml

Кстати, необходимо помнить, что каждое окно браузера (а iframe — это окно) выполняет отдельный поток яваскрипта. Чтоб запустить handler в контексте потока главного окна, можно воспользоваться примерно такой конструкцией window.parent.setTimeout(parent.handler, 0). Суть, думаю, понятна.

Применение


Я реализовал данной метод в виде самопальной библиотеки, чтобы сделать last.fm скробблер для vkontakte.ru.

Реализацию можно посмотреть на pastebin. На 58 строчке был глюк подсветки pastebin'а, поэтому я ее закомментил. Ее, конечно, надо раскомментить при использовании.

Всю работу делает объект Requester. Requester.request отправляет запрос. Ему нужно передать адрес странички веб-сервиса, на которой впоследствие будет выполняться запрос, и адрес на своем домене (то есть dummy.gif и dummy2.gif из примера). Метод request создает iframe, передает параметры и переводит iframe на dummy.gif. Возращает же он Deferred-объект, на который можно нацепить callback и errback (errback вызовется при таймауте). UserJS на ws.org/dummy.gif может получить переданные параметры у Requester.getArguments() и вернуть данные с помощью Requester.returnData(). С помощью token Requester проверяет, что данные предназначаются именно ему. То есть если есть вероятность нескольких скриптов на одном веб-сервисе, стоит поменять token на любой другой.

Работающий скрипт, использующий Requester есть здесь.
Tags:
Hubs:
Total votes 27: ↑26 and ↓1 +25
Views 1.3K
Comments Comments 30