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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28668044/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:12:58  来源:igfitidea点击:

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

javascriptiframecross-domainsame-origin-policy

提问by Hristo

I'm successfully sending a file from localhost:8888to localhost:8080(different domain in production), but I can't read the HTTP response after the transfer finishes.

我已成功从localhost:8888to 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.

未捕获的安全错误:无法从 'HTMLIFrameElement' 读取 'contentDocument' 属性:阻止了来源为“ http://localhost:8888”的框架访问来源为“ http://localhost:8080”的框架。请求访问的框架将“document.domain”设置为“localhost”,但被访问的框架没有。两者都必须将“document.domain”设置为相同的值以允许访问。

To send the file, for compatibility support, I'm trying to get this to work for <form>based file uploads; not XHRbased. This is the basic HTML structure:

为了发送文件,为了兼容性支持,我试图<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>

To insert the <iframe>element into the DOM, I do the following:

要将<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...
});

Before I submit the form, I set the actionattribute on the <form>to be the target cross-domain URL:

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

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

After submitting the <form>, the <iframe>'s loadevent is fired, and I try to access the <iframe>'s content to read the HTTP response. However, doing so throws the above error since this is is a cross-origin request, and I don't have access to the <iframe>'s content.

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

I thought the document.domainhack would work, but the error message is telling me that the iframedid not set the domain to localhost, even though I set the iframe's srcattribute to the domainHackvariable, which seems to execute.

我认为document.domainhack 会起作用,但错误消息告诉我iframe没有将域设置为localhost,即使我将iframesrc属性设置为domainHack变量,该变量似乎正在执行。

Any ideas as to what I might be doing wrong? How can I set document.domainto be localhostfor both the <iframe>and its parent (which is the current page).

关于我可能做错了什么的任何想法?如何设置document.domainlocalhost两者的<iframe>和它的父(即当前页)。



I've read through several StackOverflow questions, some MDN articles, and other random results on Google, but I haven't been able to get this to work. Some stuff I've looked through already:

我已经阅读了几个 StackOverflow 问题、一些 MDN 文章以及 Google 上的其他随机结果,但我一直无法让它发挥作用。我已经看过的一些东西:

采纳答案by Hristo

After digging around to try and figure this out, I finally came across a solution that seems to work for me. However, it isn't an exact answer to my question.

在四处寻找并试图解决这个问题之后,我终于找到了一个似乎对我有用的解决方案。但是,这不是我问题的确切答案。

In summary, I'm working on supporting <form>based file uploads. For browsers that do not support file upload via XHR, we have to resort to the traditional <form>submission, using a hidden <iframe>to avoid page refresh. The form redirects the reload action into the hidden <iframe>, and then the HTTP response is written to the body of the <iframe>after the file has been transferred.

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

Due to the same-origin policy, and hence the reason I asked this question, I don't have access to the <iframe>'s content. The same-origin policy for <iframe>s restricts how a document or script loaded from one origin can interact with a resource from another origin. Since we can't get access to the <iframe>'s document, which is where the file upload HTTP response gets written to, the server will return a redirect response to which the server will append the upload response JSON. When the <iframe>loads, the JS will parse out the response JSON, and write it to the <iframe>'s body. Finally, since the redirect was to the same origin, we can access the <iframe>'s contents :)

由于同源政策,因此我问这个问题的原因,我无权访问<iframe>的内容。<iframe>s的同源策略限制了从一个源加载的文档或脚本如何与来自另一个源的资源交互。由于我们无法访问<iframe>的文档,这是文件上传 HTTP 响应被写入的位置,服务器将返回一个重定向响应,服务器将附加上传响应 JSON。当<iframe>负载,JS会解析出响应JSON,并将其写入了<iframe>的尸体。最后,由于重定向是同一个来源,我们可以访问<iframe>的内容:)

A huge thanks to jQuery File Uploader; they did all the hard work ;)

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

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

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

Setting Up...

配置...

JS

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 - redirect page

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>

The only thing left to do is set the actionattribute on the <form>to the cross-domain URL that we're sending the upload to:

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

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

Meanwhile, on the server...

同时,在服务器上...

// 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\"}");

I know this is a massive hack to get around the same-origin policy with <iframe>s, but it seems to work, and I think it's pretty good with cross-browser compatibility. I haven't tested it in all browsers, but I'll get around to doing that, and post an update.

我知道这是绕过与<iframe>s的同源策略的大规模黑客攻击,但它似乎有效,而且我认为跨浏览器兼容性非常好。我还没有在所有浏览器中测试过它,但我会尝试这样做,并发布更新。

回答by user732456

Well, I came to solution also. It's a bit hard coded atm, but still looks neat.

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

First I made an html file on the server. (I'll modify it to ejs template later, that includes my data).

首先,我在服务器上制作了一个 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>

Important part here is the use of parent.

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

Than from my node server I'm uploading the file and sending back to the client the html file:

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

res.sendFile('file.html');

On the client I have the same html like you.

在客户端,我和你有同样的 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>', 

This template I rendered on the page. I added the following event Handler also

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

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

As you know addEventListener is not working on all browsers. And this solution will not work on IE8 < 8, which does not support postMessage. Hope this is hapefull to you

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