是否可以实现异步跨域文件上传?

发布于 2024-11-24 14:02:07 字数 2397 浏览 1 评论 0 原文

#这是可能的!请阅读下文。


首先,让我用这个图来解释一下如何实现异步文件上传


抱歉。我已经关闭了我的一个域,该图像现在消失了。不过,这确实是一个很好的图像。这是在我发现 Stack Overflow 允许通过 Imgur 上传图像之前。


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

这是可行的。但是,我面临的问题是服务器端脚本位于不同的域。 FORM提交是一个跨域的HTTP请求。现在,服务器端脚本启用了 CORS,这使我的网页有权读取从我的页面向该脚本发出的 HTTP 请求的响应数据 - 但只有当我通过 AJAX 接收 HTTP 响应时,这才有效,因此,JavaScript。

然而,在这种情况下,响应是针对 IFRAME 元素的。一旦 XML 响应到达 IFRAME,其 URL 将是删除脚本 - 例如 http://remote-domain.example/script.pl

不幸的是,CORS 没有涵盖这种情况(至少我认为)——我无法读取 IFRAME 的内容,因为它的 URL 与页面的 URL 不匹配(不同的域)。我收到此错误:

不安全的 JavaScript 尝试通过 URL 访问框架 hxxp://remote-domain.example/script.pl 来自带有 URL 的框架 hxxp://example.com/outer.html。域、协议和端口必须 匹配。

由于 IFRAME 的内容是 XML 文档,因此 IFRAME 内部没有可以使用 postMessage 或其他内容的 JavaScript 代码。

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

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

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

  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.example/script.pl', true);
xhr.onreadystatechange = responseHandler;
xhr.send(fd);

上面的方法执行异步文件上传,这相当于上图中描述的常规文件上传,并通过提交此表单来实现:

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

#Like a Boss :)

#It is possible! Read below.


First of all, let me use this diagram to explain how asynchronous file uploads can be achieved:


Sorry. I've shut down one of my domains, and the image is gone now. It was a really nice image though. This was before I found out that Stack Overflow enables uploading images via Imgur.


As you can see, the trick is to let the HTTP-response load into a hidden IFRAME element instead of the page itself. (This is done by setting the target property of the FORM element when submitting the FORM with JavaScript.)

This works. However, the problem I'm facing is that the server-side script is on a different domain. The FORM-submit is a cross-domain HTTP-request. Now, the server-side script has CORS enabled which gives my web-page the rights to read the response-data of HTTP-requests made from my page to that script - but that works only if I receive the HTTP-response via AJAX, ergo, JavaScript.

However, int this case, the response is directed towards the IFRAME element. And once the XML response lands into the IFRAME, its URL will be the remove script - e.g. http://remote-domain.example/script.pl.

Unfortunately, CORS does not cover this case (at least I think) - I am not able to read the contents of the IFRAME since its URL doesn't match the URL of the page (different domain). I get this error:

Unsafe JavaScript attempt to access frame with URL
hxxp://remote-domain.example/script.pl from frame with URL
hxxp://example.com/outer.html. Domains, protocols and ports must
match.

And since the contents of the IFRAME is an XML document, there is no JavaScript code inside the IFRAME which could make use of postMessage or something.

So my question is: How can I get the XML contents from the IFRAME?

As I said above, I am able to retrieve cross-domain HTTP-responses directly (CORS enabled), but it seems that I am not able to read cross-domain HTTP-responses once they load into an IFRAME.

And as if this question is not unsolvable enough, let me exclude these solutions:

  1. easyXDM and similar techniques which require an end-point on the remote domain,

  2. altering the XML response (to include a SCRIPT element),

  3. server-side proxy - I understand that I could have a server-side script on my domain which could serve as a proxy.

So, apart from those two solutions, can this be done?


#It can be done!!

It turns out that it is possible to forge a XHR-request (Ajax-request) which imitates a multipart/form-data FORM submit (which is used in the image above to upload the file to the server).

The trick is to use FormData constructor - read this Mozilla Hacks article for more information.

This is how you do it:

// 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.example/script.pl', true);
xhr.onreadystatechange = responseHandler;
xhr.send(fd);

The above method executes an asynchronous file-uplaod, which is equivalent to the regular file-upload described in the image above and achieved by submitting this form:

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

#Like a Boss :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

风月客 2024-12-01 14:02:07

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

如果必须以其他方式执行此操作,请使用 postMessage 与框架进行协商。

由于 IFRAME 的内容是 XML 文档,因此 IFRAME 内部没有可以使用 postMessage 或其他内容的 JavaScript 代码。

这如何阻止你?在 HTML 或 SVG 命名空间下包含脚本元素 (

Just send a cross-domain XHR request with the data from the form instead of submitting the form. CORS is only for the former.

If you must do it the other way, negotiate with the frame using postMessage.

And since the contents of the IFRAME is an XML document, there is no JavaScript code inside the IFRAME which could make use of postMessage or something.

How does that stop you? Include a script element under the HTML or SVG namespace (<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>) anywhere in the XML.

陪我终i 2024-12-01 14:02:07

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

  • 解决方案是通过代理本地 Web 服务器上的请求来使远程 Web 服务在本地可用。这会给你的本地网络服务器带来额外的负载,所以我可以想象这是不可行的。如果文件很小且不频繁,那么这会很好。

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

  • 将整个页面放入在远程服务器上运行的 iframe 中。这可能只是解决问题,但如果 XML 输出是某个过程的最后一步,那么这是非常可行的。

我确信您有充分的理由将处理服务器置于不同的域中,但如果不是,您就不会遇到所有这些问题。也许值得重新考虑?

I think it can't be done with the way you're describing. Normally if you have cross domain issues you can solve it by a JSONp approach but that only works for GET requests. With HTML5 you could potentially send binary with the GET request but that's just iffy.

  • A solution would be to make the remote webservice available locally by proxying the request on the local webserver. This will cause additional load for your local webserver so I can imagine that it is infeasible. If the files are small and infrequent though, this will do nicely.

  • Another solution would be to start polling the server after you've sent the file. You could send along a token and poll the status of the server using regular JSONp. This way you don't need to read from the iframe.

  • Put the whole page in an iframe that runs on the remote server. This might just move the problem, but if the XML output is the final step in some process it's quite feasible.

I'm sure you have a good reasons for the processing server to be on a different domain, but if it weren't you wouldn't have all these problems. Perhaps it's worthwhile to reconsider?

韵柒 2024-12-01 14:02:07

如果可以,请返回 HTML 页面而不是 XML。

在该页面中,您可以在 SCRIPT 标记中使用命令:parent.postMessage

如果您必须支持较旧的浏览器(主要是window.name 适用于低于 2Mb 的邮件。

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

另一种技术是使用 setInterval ,它将使用 JSONP 了解状态。

无论如何,您都需要远程域的配合才能获取数据。

If you can, return an HTML page instead of the XML.

In that page you can use in a SCRIPT tag the command:parent.postMessage

If you have to support older browsers(< IE8 mainly), you can write and read window.name for messages below 2Mb.

Both techniques allows you to pass string data between frames of different domains.

Another technique is to use a setInterval that will call repeatedly the remote domain from the parent page using JSONP to know the status.

In any case, you will need a cooperation from the remote domain to get the data.

别想她 2024-12-01 14:02:07

以下方法适用于我的设置(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"); ?>

This is a简单的方法 - 然而,它可能被问题的条件(2)排除。我已将其添加到此处以使我的答案完整。

Mahemoff乔治·奥伯格

The following approach is working in my setup (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>

It is working in Chrome as well, but it is needed to exclude a first onload call after the loading of the parent page. This is easily accomplished by setting a "global" variable which is tested in process().

ADDITION

The method works together with a form

<form action="URL" method="post" enctype="multipart/form-data" target="load_target">

which is submitted to URL. This URL needs to reside on the same domain as the parent page page.html. If data from a REMOTE_URL are to be downloaded, then URL would be a PHP proxy.php on the own domain with the content

<?php echo file_get_contents("REMOTE_URL"); ?>

This is a simple approach - however, it is probably excluded by the condition (2) of the question. I have added it here to make my answer complete.

Other approaches, considering iframes only, are discussed by Mahemoff and Georges Auberger.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文