Можно ли выполнить асинхронную междоменную загрузку файлов? - PullRequest
47 голосов
/ 16 июля 2011

Это возможно! Читайте ниже.


Прежде всего, позвольте мне использовать эту диаграмму, чтобы объяснить, как асинхронная загрузка файлов может быть достигнута:


Извините. Я закрыл один из своих доменов, и изображение исчезло. Это было действительно хорошее изображение, хотя. Это было до того, как я узнал, что Stack Overflow позволяет загружать изображения через Imgur.


Как видите, хитрость заключается в том, чтобы HTTP-ответ загружался в скрытый элемент IFRAME вместо самой страницы. (Это делается путем установки свойства target элемента FORM при отправке FORM с помощью JavaScript.)

Это работает. Однако проблема, с которой я сталкиваюсь, заключается в том, что серверный скрипт находится в другом домене . FORM-submit - это междоменный HTTP-запрос. Теперь на серверном сценарии включена функция CORS, которая дает моей веб-странице права на чтение данных ответа HTTP-запросов, сделанных с моей страницы, на этот сценарий - но это работает, только если я получаю HTTP-ответ через Ajax, эрго, JavaScript.

Однако в этом случае ответ направлен на элемент IFRAME. И как только XML-ответ попадает в IFRAME, его URL будет сценарий удаления, например 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.

И если этот вопрос не является достаточно неразрешимым, позвольте мне исключить эти решения :

  1. easyXDM и аналогичные методы, которые требуют конечной точки в удаленном домене,

  2. изменение ответа XML (для включения элемента SCRIPT),

  3. прокси на стороне сервера - я понимаю, что в моем домене может быть серверный скрипт, который может служить прокси.

Итак, кроме этих двух решений, это можно сделать?


Это можно сделать !!

Оказывается, можно подделать XHR-запрос (Ajax-запрос), который имитирует отправку multipart/form-data FORM (которая используется на изображении выше для загрузки файла на сервер).

Хитрость в том, чтобы использовать 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);

Приведенный выше метод выполняет асинхронную загрузку файлов, что эквивалентно обычной загрузке файлов, описанной на рисунке выше, и достигается путем отправки этой формы:

<form action="http://remote-domain.com/script.pl" 
        enctype="multipart/form-data" method="post">
    <input type="file" name="file">
</form>

Как босс:)

Ответы [ 4 ]

9 голосов
/ 06 августа 2011

Просто отправьте междоменный запрос XHR с данными из формы вместо отправки формы. CORS только для первых.

Если вы должны сделать это по-другому, договоритесь с фреймом, используя postMessage.

И поскольку содержимое IFRAME является документом XML, внутри IFRAME нет кода JavaScript, который мог бы использовать postMessage или что-то подобное.

Как это тебя останавливает? Включите элемент сценария в пространство имен HTML или SVG (<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>) в любом месте XML.

0 голосов
/ 07 августа 2011

Я думаю, что это невозможно сделать так, как вы описываете. Обычно, если у вас есть междоменные проблемы, вы можете решить их с помощью подхода JSONp, но это работает только для запросов GET. С HTML5 вы можете отправить двоичный файл с запросом GET, но это просто сомнительно.

  • Решением было бы сделать удаленный веб-сервис доступным локально, передав запрос на локальный веб-сервер. Это вызовет дополнительную нагрузку на ваш локальный веб-сервер, поэтому я могу представить, что это невозможно. Если файлы небольшие и нечастые, это будет хорошо.

  • Другим решением будет начать опрос сервера после отправки файла. Вы можете отправить токен и опросить состояние сервера, используя обычный JSONp. Таким образом, вам не нужно читать из iframe.

  • Поместите всю страницу в iframe, который запускается на удаленном сервере. Это может просто устранить проблему, но если вывод XML является последним шагом в каком-либо процессе, это вполне осуществимо.

Я уверен, что у вас есть веские основания для того, чтобы сервер обработки находился в другом домене, но если бы это было не так, у вас не было бы всех этих проблем. Возможно, стоит пересмотреть?

0 голосов
/ 05 августа 2011

В моей настройке работает следующий подход (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) вопроса. Я добавил это здесь, чтобы закончить свой ответ.

Другие подходы, касающиеся только фреймов, обсуждаются Mahemoff и Georges Auberger .

0 голосов
/ 16 июля 2011

Если можете, верните HTML-страницу вместо XML.
На этой странице вы можете использовать в теге SCRIPT команду: parent.postMessage

Если вам требуется поддержка старых браузеров (в основном window.name для сообщений размером менее 2 МБ.

Оба метода позволяют передавать строковые данные между фреймами разных доменов.

Другой метод - использовать setInterval, который будет повторно вызывать удаленный домен с родительской страницы, используя JSONP , чтобы узнать состояние.

В любом случае для получения данных вам потребуется сотрудничество с удаленного домена.

...