Возможно ли выполнить асинхронную загрузку файлов по нескольким доменам?
Это возможно! Читайте ниже.
Прежде всего, позвольте мне использовать эту диаграмму, чтобы объяснить, как можно добиться асинхронной загрузки файлов:
К сожалению. Я закрыл один из моих доменов, и изображение исчезло. Это был действительно хороший образ. Это было до того, как я узнал, что Qaru позволяет загружать изображения через Imgur.
Как вы можете видеть, трюк заключается в том, чтобы загрузить HTTP-ответ в скрытый элемент IFRAME вместо самой страницы. (Это делается установкой свойства target
элемента FORM при отправке FORM с помощью JavaScript.)
Это работает. Однако проблема, с которой я сталкиваюсь, заключается в том, что серверная сторона script находится в другом домене. FORM-submit - это междоменный HTTP-запрос. Теперь серверная сторона script имеет включенную CORS, которая дает моей веб-странице права на чтение данных ответа HTTP-запросов, сделанных с моей страницы, на этот script - но это работает только в том случае, если я получаю HTTP- ответ через Ajax, ergo, JavaScript.
Однако, в этом случае ответ направлен на элемент IFRAME. И как только XML-ответ попадет в IFRAME, его URL-адрес будет удаляться script - например. http://remote-domain.com/script.pl
.
К сожалению, CORS не охватывает этот случай (по крайней мере, я думаю). Я не могу прочитать содержимое IFRAME, так как его URL-адрес не соответствует URL-адресу страницы (другой домен). Я получаю эту ошибку:
Небезопасная попытка JavaScript для доступа к фрейму с URL-адресом hxxp://remote-domain.com/ script.pl из фрейма с URL-адресом hxxp://my-domain.com/outer.html. Домены, протоколы и порты должны матч.
И поскольку содержимое IFRAME является XML-документом, внутри IFRAME нет кода JavaScript, который мог бы использовать postMessage
или что-то в этом роде.
Итак, мой вопрос: Как я могу получить содержимое XML из IFRAME?
Как я уже говорил выше, я могу напрямую получить междоменные HTTP-ответы (CORS включен), но кажется, что я не могу читать междоменные HTTP-ответы после их загрузки в IFRAME.
И как если бы этот вопрос не был достаточно неразрешим, позвольте мне исключить эти решения:
-
easyXDM и аналогичные методы, которые требуют конечной точки в удаленном домене,
-
изменение ответа XML (включение элемента script),
-
серверный прокси - я понимаю, что у меня может быть серверный script в моем домене, который может служить прокси.
Итак, не считая этих двух решений, можно ли это сделать?
Это можно сделать!!
Оказывается, можно подделать XHR-запрос (Ajax-запрос), который имитирует submit multipart/form-data
FORM submit (который используется на изображении выше для загрузки файла на сервер).
Хитрость заключается в использовании конструктора FormData
- прочитайте эту статью Mozilla Hacks для получения дополнительной информации.
Вот как вы это делаете:
// STEP 1
// retrieve a reference to the file
// <input type="file"> elements have a "files" property
var file = input.files[0];
// STEP 2
// create a FormData instance, and append the file to it
var fd = new FormData();
fd.append('file', file);
// STEP 3
// send the FormData instance with the XHR object
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://remote-domain.com/script.pl', true);
xhr.onreadystatechange = responseHandler;
xhr.send(fd);
Вышеупомянутый метод выполняет асинхронный файл-uplaod, который эквивалентен обычной загрузке файла, описанной на изображении выше, и достигается путем отправки этой формы:
<form action="http://remote-domain.com/script.pl"
enctype="multipart/form-data" method="post">
<input type="file" name="file">
</form>
Как босс:)
Ответы
Ответ 1
Просто отправьте кросс-доменный запрос XHR с данными из формы вместо отправки формы. CORS только для первого.
Если вы должны сделать это другим способом, перейдите к кадру с помощью postMessage.
И поскольку содержимое IFRAME является XML-документом, внутри IFRAME не существует кода JavaScript, который мог бы использовать postMessage или что-то в этом роде.
Как это останавливает вас? Включите элемент script в пространстве имен HTML или SVG (<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>
) в любом месте XML.
Ответ 2
Я думаю, что это невозможно сделать с тем, как вы описываете. Обычно, если у вас есть проблемы с перекрестным доменом, вы можете решить его с помощью подхода JSONp, но это работает только для запросов GET. С HTML5 вы можете отправить двоичный код с помощью запроса GET, но это просто iffy.
-
Решение состоит в том, чтобы сделать удаленный веб-сервис доступным локально, проксируя запрос на локальном веб-сервере. Это вызовет дополнительную нагрузку для вашего локального веб-сервера, поэтому я могу себе представить, что это невозможно. Если файлы маленькие и нечастые, это будет хорошо.
-
Еще одно решение - начать опрос сервера после того, как вы отправите файл. Вы можете отправить маркер и опросить статус сервера, используя обычный JSONp. Таким образом, вам не нужно читать из iframe.
-
Поместите всю страницу в iframe, которая выполняется на удаленном сервере. Это может просто переместить проблему, но если выход XML является последним шагом в каком-то процессе, это вполне возможно.
Я уверен, что у вас есть веские причины для того, чтобы сервер обработки находился в другом домене, но если бы не вы, у вас не было бы всех этих проблем. Возможно, стоит пересмотреть?
Ответ 3
Если вы можете, верните HTML-страницу вместо XML.
На этой странице вы можете использовать в теге SCRIPT
команду: parent.postMessage
Если вам нужно поддерживать старые браузеры (в основном, IE8), вы можете писать и читать window.name
для сообщений ниже 2Mb.
Оба метода позволяют передавать строковые данные между кадрами разных доменов.
Другим методом является использование setInterval
, который будет многократно вызывать удаленный домен с родительской страницы, используя JSONP, чтобы узнать статус.
В любом случае вам понадобится сотрудничество с удаленным доменом для получения данных.
Ответ 4
В моей настройке (Firefox 3.6) работает следующий подход:
<!-- hidden target frame -->
<iframe name="load_target" id="load_target" onload="process(this);" src="#" ...>
<!-- get data from iframe after load and process them -->
<script type="text/javascript">
function process(iframe) {
var data = iframe.contentWindow.document.body.innerHTML;
// got test data="<xml><a>b</a></xml>"
}
</script>
Он также работает в Chrome, но необходимо исключить первый вызов onload после загрузки родительской страницы. Это легко осуществить, установив "глобальную" переменную, которая протестирована в process()
.
ДОБАВЛЕНИЕ
Метод работает вместе с формой
<form action="URL" method="post" enctype="multipart/form-data" target="load_target">
который отправляется на URL
. Этот URL
должен находиться в том же домене, что и родительская страница page.html
. Если данные из REMOTE_URL
должны быть загружены, то URL
будет PHP proxy.php
в собственном домене с содержимым
<?php echo file_get_contents("REMOTE_URL"); ?>
Это простой подход, однако он, вероятно, исключается из условия (2) вопроса. Я добавил его здесь, чтобы закончить мой ответ.
Другие подходы, только с учетом iframes, обсуждаются Mahemoff и Georges Auberger.