是否可以执行异步跨域文件上传?

时间:2011-07-16 16:18:26

标签: javascript html iframe file-upload cross-domain

有可能!请阅读以下内容。


首先,让我使用此图解释如何实现异步文件上传


对不起。我关闭了我的一个域名,图像现在已经消失了。这是一个非常好的形象。这是在我发现Stack Overflow允许通过Imgur上传图像之前。


正如您所看到的,诀窍是让HTTP响应加载到隐藏的IFRAME元素而不是页面本身。 (这是通过在使用JavaScript提交FORM时设置FORM元素的target属性来完成的。)

这很有效。但是,我面临的问题是服务器端脚本位于不同的域。 FORM-submit是一个跨域HTTP请求。现在,服务器端脚本启用了CORS,这使我的网页有权读取从我的页面到该脚本发出的HTTP请求的响应数据 - 但这只有在我通过Ajax接收HTTP响应时才有效, ergo,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中没有可以使用postMessage或其他内容的JavaScript代码。

所以我的问题是:如何从IFRAME获取XML内容?

正如我上面所说,我能够直接检索跨域HTTP响应(启用了CORS),但似乎我无法在加载到IFRAME后读取跨域HTTP响应。

好像这个问题不够无法解决,让我排除这些解决方案

  1. easyXDM和类似技术,需要远程域上的终点,

  2. 更改XML响应(包括SCRIPT元素),

  3. 服务器端代理 - 我知道我的域名可以有一个服务器端脚本,可以作为代理。

  4. 那么,除了这两个解决方案之外,还能做到吗?


    可以做!!

    事实证明,可以伪造一个模仿multipart/form-data FORM提交的XHR请求(Ajax请求)(在上图中用于将文件上传到服务器)。

    诀窍是使用FormData构造函数 - 阅读this Mozilla Hacks article以获取更多信息。

    您就是这样做的:

    // 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 个答案:

答案 0 :(得分:9)

只需使用表单中的数据发送跨域XHR请求,而不是提交表单。 CORS仅适用于前者。

如果你必须采用另一种方式,请使用postMessage与框架协商。

  

由于IFRAME的内容是一个XML文档,因此IFRAME中没有可以使用postMessage的JavaScript代码。

这是怎么阻止你的?在XML或SVG名称空间(<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>)下包含一个脚本元素,在XML中的任何位置。

答案 1 :(得分:0)

我认为用你描述的方式做不到。通常,如果您有跨域问题,可以通过JSONp方法解决它,但这仅适用于GET请求。使用HTML5,您可以使用GET请求发送二进制文件,但这只是iffy。

  • 解决方案是通过在本地Web服务器上代理请求,使远程Web服务在本地可用。这将导致您的本地Web服务器的额外负载,所以我可以想象它是不可行的。如果这些文件虽然很小而且很少,但这样做很好。

  • 另一种解决方案是在发送文件后开始轮询服务器。您可以使用常规JSONp发送令牌并轮询服务器的状态。这样您就不需要从iframe中读取。

  • 将整个页面放在远程服务器上运行的iframe中。这可能只会改变问题,但如果XML输出是某个过程的最后一步,那就很可行了。

我确信你有充分的理由让处理服务器在不同的域上,但如果不是,你就不会遇到所有这些问题。也许值得重新考虑一下?

答案 2 :(得分:-1)

如果可以,请返回HTML页面而不是XML。
在该页面中,您可以在SCRIPT标记中使用命令:parent.postMessage

如果您必须支持旧版浏览器(主要是&lt; IE8),您可以为2Mb以下的邮件撰写和阅读window.name

这两种技术都允许您在不同域的帧之间传递字符串数据。

另一种技术是使用setInterval,使用JSONP从父页面重复调用远程域来了解状态。

在任何情况下,您都需要远程域的合作来获取数据。

答案 3 :(得分:-1)

以下方法适用于我的设置(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()中测试的“全局”变量来轻松完成。

ADDITION

该方法与表单

一起使用
<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)所排除。我在这里添加了它以使我的答案完整。

MahemoffGeorges Auberger讨论了仅考虑iframe的其他方法。