美文网首页
跨域——知识点总结

跨域——知识点总结

作者: 他在写的 | 来源:发表于2019-03-12 15:34 被阅读0次

    浏览器同源策略:

    简单说来,只有当协议,域名,端口相同的时候才算是同一个域名,否则均认为需要做跨域的处理。

    image

    跨域方法:

    1.JSONP
    只要说到跨域,就必须聊到 JSONP,JSONP全称为:JSON with Padding,可用于解决主流浏览器的跨域数据访问的问题。

    Web 页面上调用 js 文件不受浏览器同源策略的影响,所以通过 Script 便签可以进行跨域的请求:
    1.首先前端先设置好回调函数,并将其作为 url 的参数。
    2.服务端接收到请求后,通过该参数获得回调函数名,并将数据放在参数中将其返回
    3.收到结果后因为是 script 标签,所以浏览器会当做是脚本进行运行,从而达到跨域获取数据的目的。

    实例:

    -前端

    <script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
    

    -后端

    const url = require('url');
        
    require('http').createServer((req, res) => {
    
        const data = {
        x: 10
        };
        const callback = url.parse(req.url, true).query.callback;
        res.writeHead(200);
        res.end(`${callback}(${JSON.stringify(data)})`);
    
    }).listen(3000, '127.0.0.1');
    
    console.log('启动服务,监听 127.0.0.1:3000');
    

    结果:


    image

    优点:
    1.它不像XMLHttpRequest 对象实现 Ajax 请求那样受到同源策略的限制
    2.兼容性很好,在古老的浏览器也能很好的运行
    3.不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果。

    缺点:
    它支持 GET 请求而不支持 POST 等其它类行的 HTTP 请求。
    它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面或 iframe 之间进行数据通信的问题

    2. CORS

    CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制。

    CORS 需要浏览器和服务器同时支持才可以生效,对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样。浏览器一旦发现 ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

    因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

    在前端,发送的请求与正常AJAX无区别:

    <script>
        const xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://127.0.0.1:3000', true);
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && xhr.status === 200) {
                alert(xhr.responseText);
            }
        }
        xhr.send(null);
        </script>
    

    后端:

    require('http').createServer((req, res) => {
    
        res.writeHead(200, {
        'Access-Control-Allow-Origin': 'http://localhost:8080'
        });
        res.end('这是你要的数据:1111');
    
    }).listen(3000, '127.0.0.1');
    
    console.log('启动服务,监听 127.0.0.1:3000');
    

    关键是在于设置相应头中的 Access-Control-Allow-Origin,该值要与请求头中 Origin 一致才能生效,否则将跨域失败。

    image

    成功的关键在于 Access-Control-Allow-Origin 是否包含请求页面的域名,如果不包含的话,浏览器将认为这是一次失败的异步请求,将会调用 xhr.onerror 中的函数。

    CORS 的特点:
    1.使用简单方便,更为安全
    2.支持 POST 请求方式
    3.CORS 是一种新型的跨域问题的解决方案,存在兼容问题,仅支持 IE 10 以上

    CORS的内部原理:
    跨域资源共享 CORS 详解 - 阮一峰的网络日志

    3. Server Proxy
    服务器代理,顾名思义,当你需要有跨域的请求操作时发送请求给后端,让后端帮你代为请求,然后最后将获取的结果发送给你。

    const url = require('url');
    const http = require('http');
    const https = require('https');
    
    const server = http.createServer((req, res) => {
        const path = url.parse(req.url).path.slice(1);
        if(path === 'topics') {
        https.get('https://cnodejs.org/api/v1/topics', (resp) => {
            let data = "";
            resp.on('data', chunk => {
            data += chunk;
            });
            resp.on('end', () => {
            res.writeHead(200, {
                'Content-Type': 'application/json; charset=utf-8'
            });
            res.end(data);
            });
        })      
        }
    }).listen(3000, '127.0.0.1');
    
    console.log('启动服务,监听 127.0.0.1:3000');
    
    image

    跨域请求成功。

    4. location.hash:

    改变 hash 值不会导致页面刷新,所以可以利用 hash 值来进行数据的传递,当然数据量是有限的。

    假设 localhost:8080 下有文件 cs1.html 要和 localhost:8081 下的 cs2.html 传递消息,cs1.html 首先创建一个隐藏的 iframe,iframe 的 src 指向 localhost:8081/cs2.html,这时的 hash 值就可以做参数传递。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>CS1</title>
    </head>
    <body>
        <script>
        // http://localhost:8080/cs1.html
        let ifr = document.createElement('iframe');
        ifr.style.display = 'none';
        ifr.src = "http://localhost:8081/cs2.html#data";
        document.body.appendChild(ifr);
            
        function checkHash() {
            try {
            let data = location.hash ? location.hash.substring(1) : '';
            console.log('获得到的数据是:', data);
            }catch(e) {
    
            }
        }
        window.addEventListener('hashchange', function(e) {
            console.log('获得的数据是:', location.hash.substring(1));
            });
        </script>
    </body>
    </html>
    

    cs2.html 收到消息后通过 parent.location.hash 值来修改 cs1.html 的 hash 值,从而达到数据传递。

    switch(location.hash) {
            case "#data":
            callback();
            break;
        }
        function callback() {
        const data = "some number: 1111"
        try {
            parent.location.hash = data;
        }catch(e) {
            // ie, chrome 下的安全机制无法修改 parent.location.hash
            // 所以要利用一个中间的代理 iframe 
            var ifrproxy = document.createElement('iframe');
            ifrproxy.style.display = 'none';
            ifrproxy.src = 'http://localhost:8080/cs3.html#' + data;     // 该文件在请求域名的域下
            document.body.appendChild(ifrproxy);
            }
           }
        </script>
    </body>
    </html>
    

    由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于 localhost:8080 域名下的一个代理 iframe 的 cs3.html 页面

    <script>
        parent.parent.location.hash = self.location.hash.substring(1);
    </script>
    

    这种方法的两个最大缺点:
    1.数据都暴露在了url中
    2.数据容量十分有限

    5. window.name
    window.name(一般在 js 代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个 iframe 都有包裹它的 window,而这个 window 是top window 的子窗口,而它自然也有 window.name 的属性,window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

    举个简单的例子:

    你在某个页面的控制台输入:

    window.name = "Hello World";
    window.location = "http://www.baidu.com";
    

    页面跳转到了百度首页,但是 window.name 却被保存了下来,还是 Hello World,跨域解决方案似乎可以呼之欲出了:
    首先创建 a.html 文件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>a.html</title>
    </head>
    <body>
        <script>
        let data = '';
        const ifr = document.createElement('iframe');
        ifr.src = "http://localhost:8081/b.html";
        ifr.style.display = 'none';
        document.body.appendChild(ifr);
        ifr.onload = function() {
            ifr.onload = function() {
                data = ifr.contentWindow.name;
            console.log('收到数据:', data);
            }
            ifr.src = "http://localhost:8080/c.html";
        }
        </script>
    </body>
    </html>
    

    之后在创建 b.html 文件:

    <script>
       window.name = "你想要的数据!";
    </script>
    

    http://localhost:8080/a.html 在请求远端服务器 http://localhost:8081/b.html 的数据,我们可以在该页面下新建一个 iframe,该 iframe 的 src 属性指向服务器地址,(利用 iframe 标签的跨域能力),服务器文件 b.html 设置好 window.name 的值。

    但是由于 a.html 页面和该页面 iframe 的 src 如果不同源的话,则无法操作 iframe 里的任何东西,所以就取不到 iframe 的 name 值,所以我们需要在 b.html 加载完后重新换个 src 去指向一个同源的 html 文件,或者设置成 'about:blank;' 都行,这时候我只要在 a.html 相同目录下新建一个 c.html 的空页面即可。如果不重新指向 src 的话直接获取的 window.name 的话会报错。

    6. postMessage
    postMessage 是 HTML5 新增加的一项功能,跨文档消息传输(Cross Document Messaging),目前:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能,使用起来也特别简单。

    首先创建 a.html 文件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>a.html</title>
    </head>
    <body>
        <iframe src="http://localhost:8081/b.html" style='display: none;'></iframe>
        <script>
        window.onload = function() {
            let targetOrigin = 'http://localhost:8081';
            window.frames[0].postMessage('我要给你发消息了!', targetOrigin);
        }
        window.addEventListener('message', function(e) {
            console.log('a.html 接收到的消息:', e.data);
        });
        </script>
    </body>
    </html>
    

    创建一个 iframe,使用 iframe 的一个方法 postMessage 可以想 http://localhost:8081/b.html 发送消息,然后监听 message,可以获得其他文档发来的消息。

    同样的 b.html 文件:

    <script>
        window.addEventListener('message', function(e) {
            if(e.source != window.parent) {
            return;
            }
            let data = e.data;
            console.log('b.html 接收到的消息:', data);
            parent.postMessage('我已经接收到消息了!', e.origin);
        });
    </script>
    
    image

    postMessage教程:
    Window.postMessage()

    7. document.domain

    对于主域相同而子域不同的情况下,可以通过设置 document.domain 的办法来解决,具体做法是可以在 http://www.example.com/a.htmlhttp://sub.example.com/b.html 两个文件分别加上 document.domain = "a.com";然后通过 a.html 文件创建一个 iframe,去控制 iframe 的 window,从而进行交互,当然这种方法只能解决主域相同而二级域名不同的情况,如果你异想天开的把 script.example.com 的 domain 设为 qq.com 显然是没用的。

    先创建一个 a.html 文件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>a.html</title>
    </head>
    <body>
        <script>
        document.domain = 'example.com';
        let ifr = document.createElement('iframe');
        ifr.src = 'http://sub.example.com/b.html';
        ifr.style.display = 'none';
        document.body.append(ifr);
        ifr.onload = function() {
            let win = ifr.contentWindow;
            alert(win.data);
        }
        </script>
    </body>
    </html>
    

    在创建一个 b.html 文件:

    <script>
        document.domain = 'example.com';
        window.data = '传送的数据:1111';
    </script>
    
    image

    原文参考:关于跨域,你想知道的全在这里

    相关文章

      网友评论

          本文标题:跨域——知识点总结

          本文链接:https://www.haomeiwen.com/subject/wtqnpqtx.html