如何在跨源请求后访问 iframe.contentDocument 以获得响应?

how can I access iframe.contentDocument to get response after cross-origin request?

我已成功将文件从 localhost:8888 发送到 localhost:8080(生产中的不同域),但传输完成后我无法读取 HTTP 响应。

Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://localhost:8888" from accessing a frame with origin "http://localhost:8080". The frame requesting access set "document.domain" to "localhost", but the frame being accessed did not. Both must set "document.domain" to the same value to allow access.

要发送文件,为了兼容性支持,我正在尝试让它适用于基于 <form> 的文件上传;不基于 XHR。这是基本的 HTML 结构:

<form target="file-iframe" enctype="multipart/form-data" method="POST" action="invalid">
  <input type="file" id="file-input" class="file-input" title="select files">
</form>
<iframe src="javascript:false;" id="file-iframe" name="file-iframe"></iframe>

要将 <iframe> 元素插入 DOM,我执行以下操作:

document.domain = document.domain;
var domainHack = 'javascript:document.write("<script type=text/javascript>document.domain=document.domain;</script>")';

var html = '<iframe id="file-iframe" name="file-iframe"></iframe>';
var parent = document.getElementById('wrapper');
var iframe = UTILS.createDomElement(html, parent);
iframe.src = domainHack;

UTILS.attachEvent(iframe, 'load', function(e) {

  // this throws the above SecurityError
  var doc = iframe.contentDocument || iframe.contentWindow.document;

  // do other stuff...
});

在提交表单之前,我将 <form> 上的 action 属性设置为目标跨域 URL:

action="http://localhost:8080/"

提交 <form> 后,触发 <iframe>load 事件,我尝试访问 <iframe> 的内容以读取 HTTP 响应.但是,这样做会引发上述错误,因为这是一个跨域请求,而且我无权访问 <iframe> 的内容。

我认为 document.domain hack 会起作用,但错误消息告诉我 iframe 没有将域设置为 localhost,即使我设置了 iframesrc属性给domainHack变量,好像执行了。

关于我可能做错了什么有什么想法吗?对于 <iframe> 及其父级(即当前页面),我如何将 document.domain 设置为 localhost


我已经通读了 Google 上的几个 Whosebug 问题、一些 MDN 文章和其他随机结果,但我无法让它工作。有些东西我已经看过了:

在仔细研究并尝试解决这个问题之后,我终于找到了一个似乎对我有用的解决方案。但是,这不是我问题的确切答案。

总而言之,我正在努力支持基于 <form> 的文件上传。对于不支持通过 XHR 上传文件的浏览器,我们不得不求助于传统的 <form> 提交,使用隐藏的 <iframe> 来避免页面刷新。表单将重新加载操作重定向到隐藏的 <iframe>,然后在文件传输后将 HTTP 响应写入 <iframe> 的正文。

由于 same-origin policy,因此我问这个问题的原因是,我无法访问 <iframe> 的内容。 <iframe>s 的同源策略限制了从一个源加载的文档或脚本如何与另一个源的资源交互。由于我们无法访问 <iframe> 的文档,这是写入文件上传 HTTP 响应的地方,服务器将 return 重定向响应,服务器将附加上传响应JSON。当 <iframe> 加载时,JS 将解析出响应 JSON,并将其写入 <iframe> 的主体。最后,由于重定向是同源的,我们可以访问 <iframe> 的内容 :)

非常感谢jQuery File Uploader;他们做了所有艰苦的工作;)

https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads

正在设置...

JS

function setupForm() {

  // form is declared outside of this scope
  form = document.createElement('form');
  form.setAttribute('id', 'upload-form');
  form.setAttribute('target', 'target-iframe');
  form.setAttribute('enctype', 'multipart/form-data');
  form.setAttribute('method', 'POST');

  // set the 'action' attribute before submitting the form
  form.setAttribute('action', 'invalid');
};

function setupIframe() {

  // iframe is declared outside of this scope
  iframe = document.createElement('iframe');

  /*
   * iframe needs to have the 'name' attribute set so that some versions of
   * IE and Firefox 3.6 don't open a new window/tab 
   */
  iframe.id = 'target-iframe';
  iframe.name = 'target-iframe';

  /*
   * "javascript:false" as initial iframe src to prevent warning popups on
   * HTTPS in IE6
   */
  iframe.src = 'javascript:false;';
  iframe.style.display = 'none';

  $(iframe).bind('load', function() {
    $(iframe)
      .unbind('load')
      .bind('load', function() {
        try {

          /*
           * the HTTP response will have been written to the body of the iframe.
           * we're assuming the server appended the response JSON to the URL,
           * and did the redirect correctly
           */
          var content = $(iframe).contents().find("body").html();
          response = $.parseJSON(content);

          if (!response) {
            // handle error
            return;
          }

          uploadFile(...); // upload the next file
        }
        catch (e) {
          // handle error
        }
      });
  });

  /*
   * insert the iframe as a sibling to the form. I don't think it really
   * matters where the iframe is on the page
   */
  $(form).after(iframe);
};

HTML - 重定向页面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type="text/javascript">
      // grabs the JSON from the end of the URL
      document.body.innerHTML = decodeURIComponent(window.location.search.slice(1));
    </script>
  </body>
</html>

唯一剩下要做的就是将 <form> 上的 action 属性设置为我们要将上传发送到的跨域 URL:

form.setAttribute('action', 'http://sub.example.com:8080/upload?id=ab123');
form.submit();

同时,在服务器上...

// send redirect to the iframe redirect page, where the above HTML lives
// generates URL: http://example.com/hack?%7B%22error%22%3Afalse%2C%22status%22%3A%22success%22%7D
response.sendRedirect("http://example.com/hack?{\"error\":false,\"status\":\"success\"}");

我知道这是绕过 <iframe>s 的同源策略的大规模黑客攻击,但它似乎有效,而且我认为它在跨浏览器兼容性方面相当不错。我还没有在所有浏览器中测试它,但我会抽出时间去做,并且 post 更新。

嗯,我也来解决了。它有点硬编码 atm,但看起来仍然很整洁。

首先我在服务器上创建了一个html文件。 (稍后我会将其修改为 ejs 模板,其中包括我的数据)。

<!DOCTYPE html>
<html>
<title>Page Title</title>
<script>
    function myFunction() {
        parent.postMessage('Some message!!!', 'http://192.168.0.105:3001'); // hard coded, will change this later
    }

    window.onload=myFunction;
</script>
<body>
</body>
</html>

这里重要的部分是 parent 的使用。

我从我的节点服务器上传文件并将 html 文件发回客户端:

res.sendFile('file.html');

在客户端我和你一样html

'<form id="{id}_form" action="http://192.168.0.105:3011/private/profile_picture/upload" enctype="multipart/form-data" method="post" target="{id}_uploadframe">',
'<span id="{id}_wrapper" class="file-wrapper">',
    '<input id="{id}_real" type="file" accept="image/*" name="photo" />',
    '<span class="button">{0}</span>',
'</span>',
'</form>',
'<iframe id="{id}_uploadframe" name="{id}_uploadframe" class="mc-hidden"></iframe>', 

我在页面上呈现的这个模板。我还添加了以下事件处理程序

window.addEventListener('message',function(event) {
    //if(event.origin !== cross_domain) return;
    console.log('message received:  ' + event.data,event);
},false);

如您所知,addEventListener 并非适用于所有浏览器。并且此解决方案不适用于不支持 postMessage 的 IE8 < 8。希望这对你有帮助