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

    В отличие от расширения 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 есть здесь.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 30

      +1
      [немного ot] в одной из поделок использовал кросс-доменное взаимодействие с помощью postMessage() API (HTML 5)
        0
        Тоже вариант. Это действительно избавило б от плясок с window.name, но сам принцип это не отменяет.
          0
          это для FF3, а как вы обеспечивали кроссбраузерность? (для IE 6-7, FF2 например, какой механизм использовался)
            0
            а никак, конкретно в моем случае писалось расширение для greasemonkey — вопрос кроссбраузерности не стоял
          +3
            0
            прикольно, примерно то же самое, только код более аккуратный ;)
            Жалко, я этот топик не нашел, когда искал…
              0
              Не работает в Opera 9.64 (хотя работает в 10.10)
              Проблема с Stack.
              0
              извеняюсь за офтоп, а в чем нарисована диаграммка? =)
              0
              Давно хотел скромблер для контакта сделать, у самого руки до доходили уже месяц. Большое спасибо!
                0
                чето не работает…
                  0
                  почти заработало… скачал файл, переименовал как мне удобно будет. и поэтому не работало…
                  но файл, помню где-то читал, должен заканчиватся на ".user.js"
                  а вот подключатся к Lastfm все равно не хочет :(
                    0
                    Какая версия браузера?
                      0
                      Opera AC 9.64
                      все время ошибка по таймауту
                        0
                        Странно. Наверное, какое-то из расширений из AC мешает. Будет время — разберусь.
                          0
                          +1, 9.64 (не АС) — постоянно Timeout.

                          Пробовал уже и другие скрипты отключать для вконтакта и куки чистить.
                  0
                  и кстати не проще было всё @INCLUDE заменить двумя строчками(без подчеркивания), как это делается для других userjs в Опере.
                  _http://vkontakte.ru/*
                  _http://*.vkontakte.ru/*
                    0
                    Таким образом будут обрабатываться странички с адресом malwarescript.org/evil.hack.vkontakte.ru/willdeleteyourmailbox.php.
                      0
                      обрабатыватся будут… но странице то ничего не будет (вроде :) )
                        0
                        Злая страница потенциально сможет отправлять кросс-доменные запросы от имени пользователя.
                          +1
                          угу. отключите javascript в браузере)
                            0
                            э… зачем?
                              –1
                              Чтоб злая страница не отправляла кросс-доменные запросы.
                              Спасибо за минус.
                    0
                    К слову пришлось: виджеты в Опере могут использовать кросс-доменные запросы безо всяких плясок с бубном.
                    0
                    а опере 10 что то не работает… :)
                    хей, выкладывай на github!
                      0
                      первый шаг — в опере надо вызывать всё это добро на DOMContentLoaded
                        0
                        а нет. это я ступил, что удалил .user)
                        «в опере он совсем не работал потому что сразу вызывался
                        если файл *.user.js, а не *.js, то скрипт вызывается после загрузки всей страницы».
                        0
                        гитхаб не гитхаб, но в Меркьюриале репозиторий есть: code.google.com/p/vkontakte-scrobbler/

                      Only users with full accounts can post comments. Log in, please.