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

时间:2015-02-23 06:30:32

标签: javascript iframe cross-domain same-origin-policy allow-same-origin

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

  

Uncaught SecurityError:无法阅读' contentDocument'属性   来自' HTMLIFrameElement':使用原点阻止了一个框架   " http://localhost:8888"从访问原始框架   " http://localhost:8080&#34 ;.请求访问集的帧   " document.domain的" to" localhost",但正在访问的帧确实如此   不。两者都必须设置" document.domain"允许相同的值   访问。

要发送文件,为了获得兼容性支持,我尝试将其用于基于<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...
});

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

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

提交<form>后,<iframe>的{​​{1}}事件被触发,我尝试访问load的内容读取HTTP响应。但是,这样做会引发上述错误,因为这是一个跨源请求,而我无法访问<iframe>的内容。

我认为<iframe> hack会有效,但错误消息告诉我document.domain没有将域设置为iframe,即使我设置了localhost {&#39;} iframe属性src变量,似乎执行。

关于我可能做错什么的任何想法?如何为domainHack及其父级(当前页面)设置document.domainlocalhost


我已经在谷歌上阅读了几个StackOverflow问题,一些MDN文章和其他随机结果,但我还没有能够让它发挥作用。我已经看过的一些东西:

2 个答案:

答案 0 :(得分:2)

在深入挖掘以试图解决这个问题之后,我终于找到了一个似乎对我有用的解决方案。但是,这不是我的问题的确切答案。

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

由于same-origin policy,因此我提出这个问题的原因是,我无法访问<iframe>的内容。 <iframe>的同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源交互。由于我们无法访问文件上载HTTP响应写入的<iframe>文档,因此服务器将返回重定向响应,服务器将向其附加上载响应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>

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

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>来解决同源策略是一次大规模破解,但它似乎有效,而且我认为它与跨浏览器的兼容性非常好。我没有在所有浏览器中测试它,但我会开始这样做,并发布更新。

答案 1 :(得分:1)

嗯,我也来解决了。它有点硬编码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>

这里重要的部分是父母的使用。

与我的节点服务器相比,我正在上传文件并将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不适用于所有浏览器。并且该解决方案不适用于IE8&lt; 8,不支持postMessage。希望这对你很有帮助