首先,让我使用此图解释如何实现异步文件上传:
对不起。我关闭了我的一个域名,图像现在已经消失了。这是一个非常好的形象。这是在我发现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响应。
好像这个问题不够无法解决,让我排除这些解决方案:
easyXDM和类似技术,需要远程域上的终点,
更改XML响应(包括SCRIPT元素),
服务器端代理 - 我知道我的域名可以有一个服务器端脚本,可以作为代理。
那么,除了这两个解决方案之外,还能做到吗?
事实证明,可以伪造一个模仿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>
答案 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)所排除。我在这里添加了它以使我的答案完整。
Mahemoff和Georges Auberger讨论了仅考虑iframe的其他方法。