В очередном проекте я столкнулся с необходимостью активно работать с кросс доменными запросами на ajax, тема, как я вижу на хабре особо не поднималась и не освещалась, вот и решил поделиться с читателями свои опытом.
Допустим мы имеем сайт
Задача: найти кроссбраузерное решение для отправки запросов которое будет работать: ie7+, opera 9.6+, ff 3+, chrome, safari. А также так как обращаться мне приходилось также с http на https не должно быть сообщения “mixed content” в ie.
В качестве библиотеки на сайте использовалась jQuery и встроенного механизма кросс доменных запросов в ней нет (появилася в 1.5 возможность организовать jsonp но это не всегда подходит), поиск плагинов также не дал результатов. Поэтому, обломавшись с простыми путями (что в принципе и ожидалось), отправился в Google, который тоже не дает очевидного ответа — я больше находил описание методов или же вещи которые мне не подходили (например в dojo есть релизая таких запросов, но менять весь js на сайте очень трудозатратно).
В качестве решения проблемы могли бы использоваться следующие техники:
Новичок, при виде этого списка ужаснётся (в принципе я тоже не в восторге был). Итак наша задача — найти оптимальный путь решения проблемы, который не создавал бы лишних костылей на сайте.
1) postMessage – это новая возможность стандарта HTML5, позволяет отсылать сообщения из одного окна в другое, при этом контент окон может быть с разных доменов. Примерная реализация targetWindow.postMessage(message, targetOrigin); targetWindow — окно куда шлём запроc, message — сообщение, targetOrigin целевой домен который должен быть открыт в окне, допускается указания “*” в качестве домена, при этом домен может быть любой.
2) JSONP (JSON Padding) или «JSON с подкладкой» является расширением JSON, когда имя функции обратного вызова указывается в качестве входного аргумента. Использовав тэг , мы можем обратиться к другим доменам, однако результат придет в виде json ответа, который мы не сможем обработать, jsonp предлагает следующее решение: передавая серверу имя функции, мы получаем в ответе не голые данные, а parseResponse({«paper»: «A4», «count»: 5}), что вызовет функцию parseResponse.
3)CORS тема большая, поэтому расскажу вкратце, кому интересно велкам в w3c документацию. XMLHTTPRequest 2 — это новая спецификация запросов, работает и вызывается как обычный XMLHTTPRequest (в ie это XDomainRequest), но теперь в http заголовок добавляется следующее:
Access-Control-Allow-Origin определяет каким доменам разрешено соединение
Access-Control-Allow-Credentials указывает полномочия на выполнения запроса (Access-Control-Allow-Credentials: «Access-Control-Allow-Credentials» ":" true true: %x74.72.75.65; «true», case-sensitive)
Access-Control-Max-Age — как долго результаты будут кэшироваться.
Также можно определить доступные методы (GET, POST), все дополнительные поля заголовка вы можете посмотреть в спецификации.
Главная проблема метода — его поддерживают далеко не все браузеры, а тем кто захотят использовать реализация от IE — XDomainRequest, не стоит забывать, что IE в своем репертуаре и не позволит вам сделать запрос с протокола http на https, хотя по спецификации запрещены только запросы с https на http ), но это ie.
4) document.domain нам подойдет, если мы хотим общаться с 2 сайтами на общем наддомене, то есть test1.name.my и test2.name.my. Между этими сайтами нормальный запрос не сделать, однако у document есть свойство domain, которое мы можем сменить для обоих окон на name.my и общаться обычным способом через iframe.
5) window.name Transport — заключается в изменении свойста name у windows и передачи таким образом через текст свойства наших сериализованных данных.
6) Server-side proxy — самое простое решение — это проксирование запроса сервером. То есть, name.my делает специальный запрос, например, на особенный URL типа name.my/proxy/name2.my/test.html, и сервер name.my проксирует его на name2.my/test.html.
7) CRAZY IFRAME STUFF — суть метода в том, что ифреймы, даже находясь на разных доменах, могут общаться друг с другом при помощи изменения идентификаторов фрагментов адресов (идентификатор – то, что стоит в адресе после '#') Путем последовательных изменений #фрагмента образуется поток данных, который может передаваться в обе стороны. Т.к идентификатор фрагмента — текст, то все данные для передачи приходится (де)сериализовать, т.е превращать в JSON + понадобится 2 iframe, дабы не затрагивать адрес видимого окна.
8) flash — использовать флеш в качестве промежуточного интерфейса (отдельная тема, все тонкости которой также тянут на статью)
Поискав в интернете нашел замечательную библиотеку easyxdm.net/wp. Самое вкусное заключается в методе работы, выбирается самый быстрый и современные метод основываясь на браузере и его версии.
Для остальных браузеров которые не поддерживают postMessage будет использоваться технология основанная на обмене #hash Функции библиотеки не ограничиваются только ajax но разговор будет в основном о нем.
Итак начну c реализации ajax запроса при помощью easyxdm.
Создаем непосредственно прокси объект для отсылки запросов:
easyXDM – это, если так можно назвать статический класс, который не имеет конструктора и реализует паттерн синглтон.
Шлем сам запрос:
Теперь более подробно: первым параметром мы задаем основные настройки, в частности тут указываем путь к заранее подготовленному файлу “провайдеру” (лежит в архиве с сборкой в папке cors), который должен находи
я на удаленном сервере, он будет проксировать запросы на нужный нам url и посылать нам ответы одним из доступных методов.
Однако не стоит забывать что сюда также надо будет вписать путь до флешки (флешка также прикладывается к либе), через которую будут устанавливать соединение старые версии IE.
Далее мы создаем структуру удаленных методов, сейчас там объявлен request ( в файле name.my/cors/index.html также должна быть создана аналогичная структура методов)
Вызовом функции xhr.request мы отсылаем запрос нашему прокси файлу name.my/cors/index.html, который стучится по url и передает туда параметр data, используя обычный ajax, получив ответ, он шлёт данные (при необходимости разбивая ответ на части) обратно к нашему хосту, где вызывается callback, который мы передали последним параметром.
Немного затронем файл name.my/cors/index.html он является незаменимой частью нашего запроса, во-первых он организует обратный транспорт и прием данных. Во вторых у него есть замечательная плюшка — он реализует CORS своими средствами:
var useAccessControl = true; // разрешаем (true) или запрещаем (false) принимать заголовки от сервера (чтобы случайно сервером не разрешить отсылку данных на сторонний домент)
var alwaysTrustedOrigins = [(/\.?easyxdm\.net/), (/xdm1/)]; // список разрешенных серверов
var remote — объект easyXDM, в котором мы должны реализовать структуру удаленных методов.
А также не забываем подключать маленькую “библиотечку” json2.js которая для старых браузером реализует объект JSON, активно использующийся в easyXDM (при этом easyXDM не мешает вам создать свой сериализатор и передать его в качестве параметра, хотя мне кажется это очень редкая потребность).
У меня в проекте стояла задача слать по аяксу много разных форм, соответственно нужен был удобный инструмент который сериализует и шлет по ajax указанную форму. Выход нашелся сразу в лице плагина jQuery Form, но плагин завязан на jq и следовательно слать кросс доменные запросы отказывается.
Приведу простой способ как внедрить наш кросс доменный запрос в плагин не затронув структуры и оставив jQuery в качестве обработчика обычного ajax.
Имеем в html форму (то что внутри формы нас мало волнует этим займется плагин):
Заранее подготовим наш объект для кросс доменного запроса:
Далее приведен код подключения плагина, в котором все поля формы автоматически сериализуются, а url на который мы шлем форму берется из action самой формы (как и метод)
Это метод в котором мы обрабатываем результат, его мы вообще не затрагиваем:
А вот главный фокус будет в beforeSubmit, метод вызывается перед отправкой формы, соответственно тут мы можем как получить уже сериализованные данные так и прервать дальнейшую отправку запроса (что нам и надо):
Примерно так можно легко решить проблематичную задачу. Работающие примеры можно найти на сайте библиотеки. На этом я завершаю свой первый «хабрапост».
Сама библиотека
Допустим мы имеем сайт
http://name.myи хотим на нем использовать ajax и что же мы получим?
- Запрос на
http://name.my/ajax.php
(буду писать везде php, чтобы всем было удобно, сам же работал с java) вернет нам ответ без проблем - Запрос на
http://google.com/
завершится ошибкой доступа - Попытка достучаться до
https://name.my/ajax.php
тоже окончится неудачей, сменился протокол http://name.my:81/ajax.php
и другой порт в адресе испортит малину
Задача: найти кроссбраузерное решение для отправки запросов которое будет работать: ie7+, opera 9.6+, ff 3+, chrome, safari. А также так как обращаться мне приходилось также с http на https не должно быть сообщения “mixed content” в ie.
В качестве библиотеки на сайте использовалась jQuery и встроенного механизма кросс доменных запросов в ней нет (появилася в 1.5 возможность организовать jsonp но это не всегда подходит), поиск плагинов также не дал результатов. Поэтому, обломавшись с простыми путями (что в принципе и ожидалось), отправился в Google, который тоже не дает очевидного ответа — я больше находил описание методов или же вещи которые мне не подходили (например в dojo есть релизая таких запросов, но менять весь js на сайте очень трудозатратно).
В качестве решения проблемы могли бы использоваться следующие техники:
- postMessage
- JSONP
- CORS
- document.domain methods
- window.name Transport
- Server-side proxy
- CRAZY IFRAME STUFF
- flash
Новичок, при виде этого списка ужаснётся (в принципе я тоже не в восторге был). Итак наша задача — найти оптимальный путь решения проблемы, который не создавал бы лишних костылей на сайте.
1) postMessage – это новая возможность стандарта HTML5, позволяет отсылать сообщения из одного окна в другое, при этом контент окон может быть с разных доменов. Примерная реализация targetWindow.postMessage(message, targetOrigin); targetWindow — окно куда шлём запроc, message — сообщение, targetOrigin целевой домен который должен быть открыт в окне, допускается указания “*” в качестве домена, при этом домен может быть любой.
2) JSONP (JSON Padding) или «JSON с подкладкой» является расширением JSON, когда имя функции обратного вызова указывается в качестве входного аргумента. Использовав тэг , мы можем обратиться к другим доменам, однако результат придет в виде json ответа, который мы не сможем обработать, jsonp предлагает следующее решение: передавая серверу имя функции, мы получаем в ответе не голые данные, а parseResponse({«paper»: «A4», «count»: 5}), что вызовет функцию parseResponse.
3)CORS тема большая, поэтому расскажу вкратце, кому интересно велкам в w3c документацию. XMLHTTPRequest 2 — это новая спецификация запросов, работает и вызывается как обычный XMLHTTPRequest (в ie это XDomainRequest), но теперь в http заголовок добавляется следующее:
Access-Control-Allow-Origin определяет каким доменам разрешено соединение
Access-Control-Allow-Credentials указывает полномочия на выполнения запроса (Access-Control-Allow-Credentials: «Access-Control-Allow-Credentials» ":" true true: %x74.72.75.65; «true», case-sensitive)
Access-Control-Max-Age — как долго результаты будут кэшироваться.
Также можно определить доступные методы (GET, POST), все дополнительные поля заголовка вы можете посмотреть в спецификации.
Главная проблема метода — его поддерживают далеко не все браузеры, а тем кто захотят использовать реализация от IE — XDomainRequest, не стоит забывать, что IE в своем репертуаре и не позволит вам сделать запрос с протокола http на https, хотя по спецификации запрещены только запросы с https на http ), но это ie.
4) document.domain нам подойдет, если мы хотим общаться с 2 сайтами на общем наддомене, то есть test1.name.my и test2.name.my. Между этими сайтами нормальный запрос не сделать, однако у document есть свойство domain, которое мы можем сменить для обоих окон на name.my и общаться обычным способом через iframe.
5) window.name Transport — заключается в изменении свойста name у windows и передачи таким образом через текст свойства наших сериализованных данных.
6) Server-side proxy — самое простое решение — это проксирование запроса сервером. То есть, name.my делает специальный запрос, например, на особенный URL типа name.my/proxy/name2.my/test.html, и сервер name.my проксирует его на name2.my/test.html.
7) CRAZY IFRAME STUFF — суть метода в том, что ифреймы, даже находясь на разных доменах, могут общаться друг с другом при помощи изменения идентификаторов фрагментов адресов (идентификатор – то, что стоит в адресе после '#') Путем последовательных изменений #фрагмента образуется поток данных, который может передаваться в обе стороны. Т.к идентификатор фрагмента — текст, то все данные для передачи приходится (де)сериализовать, т.е превращать в JSON + понадобится 2 iframe, дабы не затрагивать адрес видимого окна.
8) flash — использовать флеш в качестве промежуточного интерфейса (отдельная тема, все тонкости которой также тянут на статью)
Поискав в интернете нашел замечательную библиотеку easyxdm.net/wp. Самое вкусное заключается в методе работы, выбирается самый быстрый и современные метод основываясь на браузере и его версии.
- IE6 and IE7 – используется связка с флешем (Microsoft Security Bulletin MS11-018)
- IE8+ – используется postMessage
- Opera 9+ – используется postMessage
- Firefox 1-2 – используются ifame
- Firefox 3+ – используется postMessage
- Safari 4+ – используется postMessage
- Chrome 2+ – используется postMessage
Для остальных браузеров которые не поддерживают postMessage будет использоваться технология основанная на обмене #hash Функции библиотеки не ограничиваются только ajax но разговор будет в основном о нем.
Итак начну c реализации ajax запроса при помощью easyxdm.
Создаем непосредственно прокси объект для отсылки запросов:
var xhr = new easyXDM.Rpc({ remote: "http://name.my/cors/index.html" // путь к провайдеру }, { remote: { request: {} } });
easyXDM – это, если так можно назвать статический класс, который не имеет конструктора и реализует паттерн синглтон.
Шлем сам запрос:
xhr.request( { url: "pathRelativeToRemote/getrest/", //адрес нашего запроса method: "POST", data: {foo:"bar"} }, function(response) { // функция обработки результата ответа alert(response.status); alert(response.data); } );
Теперь более подробно: первым параметром мы задаем основные настройки, в частности тут указываем путь к заранее подготовленному файлу “провайдеру” (лежит в архиве с сборкой в папке cors), который должен находи
я на удаленном сервере, он будет проксировать запросы на нужный нам url и посылать нам ответы одним из доступных методов.
Однако не стоит забывать что сюда также надо будет вписать путь до флешки (флешка также прикладывается к либе), через которую будут устанавливать соединение старые версии IE.
Далее мы создаем структуру удаленных методов, сейчас там объявлен request ( в файле name.my/cors/index.html также должна быть создана аналогичная структура методов)
Вызовом функции xhr.request мы отсылаем запрос нашему прокси файлу name.my/cors/index.html, который стучится по url и передает туда параметр data, используя обычный ajax, получив ответ, он шлёт данные (при необходимости разбивая ответ на части) обратно к нашему хосту, где вызывается callback, который мы передали последним параметром.
Немного затронем файл name.my/cors/index.html он является незаменимой частью нашего запроса, во-первых он организует обратный транспорт и прием данных. Во вторых у него есть замечательная плюшка — он реализует CORS своими средствами:
var useAccessControl = true; // разрешаем (true) или запрещаем (false) принимать заголовки от сервера (чтобы случайно сервером не разрешить отсылку данных на сторонний домент)
var alwaysTrustedOrigins = [(/\.?easyxdm\.net/), (/xdm1/)]; // список разрешенных серверов
var remote — объект easyXDM, в котором мы должны реализовать структуру удаленных методов.
А также не забываем подключать маленькую “библиотечку” json2.js которая для старых браузером реализует объект JSON, активно использующийся в easyXDM (при этом easyXDM не мешает вам создать свой сериализатор и передать его в качестве параметра, хотя мне кажется это очень редкая потребность).
У меня в проекте стояла задача слать по аяксу много разных форм, соответственно нужен был удобный инструмент который сериализует и шлет по ajax указанную форму. Выход нашелся сразу в лице плагина jQuery Form, но плагин завязан на jq и следовательно слать кросс доменные запросы отказывается.
Приведу простой способ как внедрить наш кросс доменный запрос в плагин не затронув структуры и оставив jQuery в качестве обработчика обычного ajax.
Имеем в html форму (то что внутри формы нас мало волнует этим займется плагин):
<form action="name2.my" id="myform" method="POST">...</form>
Заранее подготовим наш объект для кросс доменного запроса:
crossAjax = new easyXDM.Rpc({ remote: 'name2.my/cors', // наш провайдер на удаленном сервере swf: 'name.my/easyxdm.swf' }, { remote: { request: {} } });
Далее приведен код подключения плагина, в котором все поля формы автоматически сериализуются, а url на который мы шлем форму берется из action самой формы (как и метод)
$('#myform').ajaxForm({ beforeSubmit: beforeSubmit, success: showResponse });
Это метод в котором мы обрабатываем результат, его мы вообще не затрагиваем:
showResponse = function(responseText, statusText, xhr, form){ тут мы что-то делаем с нашим ответом }
А вот главный фокус будет в beforeSubmit, метод вызывается перед отправкой формы, соответственно тут мы можем как получить уже сериализованные данные так и прервать дальнейшую отправку запроса (что нам и надо):
beforeSubmit = function(arr, form, options) { if (options.url.indexOf(location.host)<0) { // если хост удаленный то используем наш метод var json = {}; for (var i = 0; i<arr.length; i++) { // преобразуем сериализованные данные формы в нормальный объект js готовый к нашей сериализации (сама форма их передает в весьма странном виде) json[arr[i].name] = arr[i].value; } crossAjax.request({ // шлем кросс доменный запрос, подставляя наши параметры url: options.url, method: "POST", data: json }, function(response) { switch (response.status) { // разбираем ответ case 200: showResponse(JSON.parse(response.data), response.status, arr, form) break; default: alert("Error: " + response.status); break; } }); return false; //останавливаем нативный ajax запрос от jQ } }
Примерно так можно легко решить проблематичную задачу. Работающие примеры можно найти на сайте библиотеки. На этом я завершаю свой первый «хабрапост».
Сама библиотека