通过 Iframes 上传到 Amazon S3

22

唉,我们又回到了这个问题。我可以在任何体面的浏览器上轻松使用CORS直接上传文件到我的AWS S3 bucket。但是(它来了),对于IE,我必须退回到Iframes。简单来说就是,设置一个隐藏Ifame,创建一个表单,将其目标设置为Iframe名称/ID,提交表单。如果上传成功,则将Iframe重定向到我指定的URL,并且我可以访问所需内容。但是,如果发生错误,由于Iframe现在位于AWS域上,我将无法访问错误的XML内容。事实上,我甚至不知道是否发生了错误。

我见过网上勇敢的人谈论在要上传的相同bucket上托管html文件,然后使用postMessages来路由Iframe内容或类似的东西。

请问有人能向我解释如何实现这个神话般的解决方案吗?Blueimp的jQuery文件上传程序似乎解决了这个问题,但天哪,代码如此地过度使用jQuery,以至于我无法理解其中的要点。

为了清晰起见而编辑

  1. IE < 10没有FileReader API。
  2. 因此不能使用XDomainRequest将文件发送到S3
  3. 因此使用Iframe和完整的表单一起发送到S3
  4. 如果成功,AWS会将其重定向到服务器上的页面,该页面读取标头,然后返回可以由客户端脚本读取的JSONP样式响应(重定向页面可以由我指定)。
  5. 如果发生错误,现在我唯一能做的就是等待超时到期,然后控制台记录一下IFRAME ID,并弹出警报框,以便用户可以按ID查询Iframe,读取糟糕的XML内容,找出AWS指定的错误,然后重试(我正在讽刺...)

为什么CORS在IE上不起作用?我使用了jquery fileupload插件,但我还没有在IE上测试过。这篇文章http://blog.appharbor.com/2013/01/10/asynchronous-browser-uploads-to-s3-and-gcs-using-cors-aspnet-mvc似乎表明它可以在IE上工作。 - d33pika
@JibiAbraham:嗯,IE从8版本开始就有CORS了,但是保持传统,它显然决定走自己的路线。XDomainRequest-限制、限制和解决方法 - Henrik Aasted Sørensen
基本的插件并不复杂: https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin 。去看一下吧。 - d33pika
@Henrik 我必须看一下。10分钟后发布。 - Jibi Abraham
xhr可以在IE上工作,但有一些例外情况:http://www.html5rocks.com/en/tutorials/cors/ - d33pika
显示剩余5条评论
4个回答

19
几乎关于jQuery文件上传插件如何进行iframe上传的所有内容都在其Iframe传输插件(以及支持result.html页面)中。作为介绍,您可能需要阅读他们在跨域上传维基页面上的用户说明,特别是跨站点iframe传输上传部分。(请注意,根据他们的浏览器支持页面,像上传进度这样的细节不支持IE<10,因此我认为使用iframe传输至少需要付出重大努力才能实现。)(另外,我不认为任何使用文件上传插件的S3上传实现都可以访问文件上传错误的XML内容)
Iframe Transport插件为jQuery添加了一种新的Ajax“传输”方法,与File Upload插件无关。您可能需要阅读jQuery.ajaxTransport()的文档,以了解jQuery提供添加新传输的API。

我将尝试总结Iframe Transport插件正在做的事情,以及它与上传文件到Amazon S3的关系:

当文件上传被触发时,将调用send()函数。此函数:
  • 创建一个隐藏的表单元素
  • 创建一个iframe元素并绑定一个load事件处理程序到iframe上,src="javascript:false;"
  • 将iframe添加到隐藏表单中,并将隐藏表单添加到文档中
当iframe被创建并且它的“页面”加载完成时,将调用其load事件处理程序。处理程序:
  • 从iframe中清除自身,并绑定另一个load事件处理程序
  • 配置隐藏表单:
    • 表单的action将是S3存储桶的URL
    • 表单的target设置为iframe,以便服务器响应在iframe中加载
    • 其他字段,例如AWSAccessKeyId,都会被添加。特别地,success_action_redirect被设置为您的服务器上result.html的URL,例如http://example.org/result.html?%s
    • 通常情况下,%s令牌应该由服务器端代码替换为上传结果,但在S3中,您可以使用成功值来硬编码这个令牌,因为只有在上传成功时Amazon才会重定向到此URL。

    • 将原始表单中的文件输入字段移动到隐藏表单中,并在原始字段的位置上留下克隆的字段
  • 提交隐藏表单
  • 将文件输入字段移回原始表单,替换克隆的字段
文件被上传到S3。如果成功,Amazon将iframe重定向到success_action_redirect URL。如果不成功,则Amazon返回一个错误,该错误也会在iframe中加载。
调用iframe的load事件处理程序。处理程序:
  • 尝试保存对iframe的document对象的引用。如果文件上传失败,则处理程序保存undefined
  • 使用成功代码和对iframe的document对象(或undefined)调用完成回调
  • 删除隐藏表单(和iframe)
在控制权返回到您的代码之前,将iframe的document对象传递给转换器(在Iframe Transport插件底部),具体取决于您期望的数据类型。转换器从document对象中提取数据并将其返回(或者如果文件上传失败则返回undefined)到您的回调函数。
调用您的回调函数(如通过jQuery.ajax()传递的success和/或complete)。插件始终返回成功代码,因此任何error回调都不会被触发。
如果传递给您的回调函数的数据是您在success_action_redirect中包含的值,则文件上传成功。如果数据为undefined,则文件上传失败。
更新:如果错误的XML页面与S3存储桶保持相同的来源,则加载到另一个iframe中的S3存储桶中的另一页可以访问原始iframe的内容(因为它们来自相同的来源)。您的主页面可以使用postMessage()(或easyXDM的FlashTransport,如果需要支持IE6/7)与第二个iframe通信。

尝试保存 iframe 文档对象的引用,这不会抛出无法捕获的错误,对吧? - Jibi Abraham
错误是可捕获的 - Jeffery To
我来试一下,除了错误处理以外,其他都准备好了,等我有消息再告诉你,谢谢。 - Jibi Abraham

3
这个问题,即如何向使用没有FileReader或FormData支持的浏览器的用户提供准确的反馈,一直困扰着我。我花了整整3天时间试图想出一个解决方案,最终只得到了接近于无解的东西。
让我们看看事实:
- 浏览器:IE8/9 // 几乎没有其他没有FileReader支持的浏览器 - 上传行为:由于用户体验原因,我们需要它是“Ajax” - 工具箱:jQuery File Upload*
那么,除了使用iframe上传文件之外,就没有其他上传文件的方法了。对吗?
所以,像@jeferry_to所描述的那样,使用jQuery Iframe Transport的jQuery文件上传工具就是这项工作的工具。
*实际上,这个工具/插件并没有改变任何东西...
现在怎么办?
好吧...我们需要在传输iframe内部访问S3响应。但是我们不能这样做,因为它在不同的域上。因此,我们决定使用涉及第二个iframe的技巧来处理它。
设置如下:
  • TopFrame,我们的页面(www.myhost.com)
  • iframe TransportFrame(s3.amazonaws.com),由插件自动生成 - 包含S3响应
  • iframe XDMFrame(s3.amazonaws.com),在需要时访问TransportFrame,获取响应并将其传递给TopFrame

情景:

首先,我们需要修改jQuery Iframe Transport,以便它不会自动删除自动生成的表单和传输框架。我们需要这样做是因为后面将使用的#postMessage本质上是异步的,我们不希望在尝试访问它时iframe已经消失了。

  1. 在TopFrame中,我们使用jQuery文件上传将文件上传到S3。现在我们想要确保它已经被上传,并在出现错误时获得错误信息。
  2. TopFrame使用#postMessage向XDMFrame发送跨域消息,提供TransportFrame的名称。该消息实际上是说:“嘿,请检查iframe X的内容,并在你完成后将其发送回给我。”
  3. XDMFrame然后执行类似于top.frames ['iframe X'] .document.documentElement 的操作,以访问TransportFrame的内容,对其进行字符串化并通过#postMessage将其发送回TopFrame。
  4. TopFrame接收消息,向用户显示适当的反馈,并删除表单和iframe,因为我们使用了jQuery Iframe Transport修改。

好了,现在应该一切正常,因为所有步骤都是按照规定进行的。

不过,你甚至不需要费心。

你看...如果你强制现代浏览器使用iframe传输而不是XHR2,则上述解决方案确实会像魔术般地工作。

然而,这毫无意义。我们希望它在IE8 + 9中工作。

嗯...在IE8/9中,它有时可以工作,有时不能。通常情况下,它不起作用。

因为IE友好的HTTP错误消息,所以为什么?没错,你读得很清楚。在出现错误时,S3会响应一个HTTP错误状态,具体取决于错误(400、403等)。现在,根据响应的状态和长度(如here所示),IE会丢弃S3的响应并替换为友好的错误消息。为了克服这个问题,您必须确保响应始终大于512字节。在这种情况下,您无法保证任何东西,因为您无法控制响应。S3可以控制,而典型的错误少于512字节。
简而言之:
iframe技巧适用于那些不需要它的浏览器,而对于那些需要它的浏览器则不起作用。
不幸的是,我想不到其他任何办法,所以这个案例对我来说已经结束了。

我真的很感激您在这么长时间后还抽出时间来回复。帖子消息是个不错的技巧,谢谢您分享。希望能有更多人发现它的用途 :) - Jibi Abraham

1
对于“postMessage”场景,也许iframe应该包含一个简单的javascript。
[编辑]针对被错误消息接管的iframes

IFRAME脚本

window.document.onload = function(e){ 
    window.parent.postMessage(document, '*'); //replace '*' with your parent if possible   
}
// just to get the proper document for the parent to target me
window.addEventListener('message',function(e) {
    if (e.domain == 'example.com') { // the domain of your parent frame
        if (e.data == "Salute") {
            window.parent.postMessage("I'm here", '*'); //replace '*' with your parent too
        }
    }
});

现在父级了解iFrame并可以跟踪其状态(取决于它是否回答简单的postMessage)。

父级脚本

var iFrameTarget;
var iFrameTakenOver = false;
var timer;
window.addEventListener('message',function(e) {
    if (e.domain == 'example.com') { // the domain of your iframe
        if (e.data) { // e.data contains the iframe document
            if(typeof(e.data) =='object')
                iFrameTarget = e.source;
            elseif(e.data == "I'm here")
            {
                iFrameTakenOver = false;
            }
            timer =setInterval(call_iFrame(),5000); // check iFrame presence in 5 seconds
        }
    }
});

function call_iFrame() {
    iFrameTarget.postMessage('Salute');
    iFrameTakenOver = true;
}

如果iframe没有用它的"code"回应,iFrameTakenOver将永久设置为false,检查这一点将验证是否发生了错误。

S3在出现错误的情况下接管了框架,不幸的是,我得出了一个悲伤的结论,那就是IE用户肯定只能安心地接受“出了点问题”而不是具体的错误信息。 - Jibi Abraham
哦,我明白了... 真遗憾,但这意味着你只需通过向所述 iFrame 发送 postMessage 即可知道它是否被接管,如果收到回复,则一切正常;如果没有收到回复,则由于错误已经被接管。 - itsid
@JibiAbraham 我更新了该脚本,以跟踪是否 iframe 已被 S3 接管。 - itsid

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接