• 跨域总结


    跨域定义

    协议、端口号、域名有一个不同就是跨域。
    主域名相同,子域名不同也是跨域,emial.aa.com和time.aa.com就是主域名相同,子域名不同的跨域
    协议不同或者端口号不同造成的跨域,前端无法解决

    1、表单默认提交(get、post)、超链接访问域外的资源,这是允许的,因为在点击按钮/超链接时,浏览器地址已经变了,这就是一个普通的请求,不存在跨域;
    2、ajax(借助xmlhttprequest)跨域请求,这是被禁止的,因为ajax就是为了接受接受响应,这违背了,不允许跨域读的原则
    3、jsonp属于跨域读且形式限制为GET方式,它利用了script标签的特性;这是允许的。因为浏览器把跨域读脚本,当作例外,类似的img、iframe的src都可以请求域外资源

    跨域的分类

    1. 提交给后端跨域

    jsonp get

    iframe + form post

    CORS 全类型

    2. 前端跨域通信

    2.1 父子页面通信

    父子页面有两种,iframe嵌套的和window.open打开的。window.open打开的页面,在窗口模式时,被打开的页面就是当前页面的子页面,
    tab模式时,只是形式不一样,也是子页面,可以通过window.opener来访问父页面。

    iframe的通信方式: window.name, window.hash, postMessage

    window.open的通信方式: postMessage

    其中postMessage通信是最简单实现双向通信的方式。

    2.2 两个独立页面通信

    两个同域的独立页面通信可以使用localStorage,兄弟页面监听storage事件就行了。

    两个跨域的独立页面可以使用一个birdge页面当做桥梁,做同域通信,两个页面分别用iframe嵌套它,用postMessage做跨域通信,就实现了两个跨域页面的通信。

    具体如下图
                 postMessage                                            storage                                                postMessage
    tab A <-------------------> iframe A [birdge.html] <-------------> iframe B [birdge.html] <------------------> tab B

    跨域解决方法

    1. jsonp(Get请求的跨域,安全性低)

    1. 生成唯一函数名callback_uuid,在window对象上注册这个函数,window[callback_uuid] = function(){}
    2. 将函数名callback_uuid发送到后端,后端生成数据JSON,拼装js文档callback_uuid(JSON),返回给客户端
    3. 客户端将script标签插入到文档中,浏览器解析script标签,自动执行callback_uuid方法
    4. 真正的callback函数是在callback_uuid中调用,而不是直接调用

    缺点: 

    1. jsonp 在调用失败时,无法获得HTTP状态码

    2. 只能支持GET请求

    代码实现如下:

    var jsonp = (function () {
        var num = 0;
        function foo(options) {
            let { url, params, callback } = options;
            num++;
            //通过闭包内的自增数字生成唯一的函数名
            var jsonCallback = 'jsoncallback_' + num;
            //注册函数到全局对象上
            window[jsonCallback] = function (data) {
                //清理回调函数名
                window[jsonCallback] = null;
                //清理script标签
                removeElement(jsonCallback);
                //调用真正的回调函数
                callback && callback(data);
            }
            //插入script标签
            var queryString = Object.keys(params).reduce((pre, cur) => {
                    return pre + '&' + cur + '=' + option.params[cur];
            }, '');
            url += 'callback=' + jsonCallback + queryStirng;
            var script = document.createElement('script');
            script.src = url;
            script.id = jsonCallback;
            document.getElementByTagName('head')[0].appendChild(script);
        }
        function removeElement(id) {
            var ele = document.getElementById(id),
                parent = ele.parentNode;
            if (parent && parent.nodeType != 11) {
                parent.removeChild(ele);
            }
        }
        return foo;
    }());

    2. CORS(各种请求均可,IE8,9只能是GET和POST请求,通用性好)

    3. iframe(传统POST的最佳选择)

    3.1 iframe + form + 302 (post提交数据,url获取返回值 )

    将<form>表单通过一个iframe来submit,将form的target属性设置为iframe的name,这样form的action URL就会在
    iframe中打开,服务器返回的数据就会输出到iframe中。通过主页面和iframe的交互,完成对数据的读取。
    举例:

    <form action="http://www.b.com/io.php" method="POST" enctype="multipart/form-data" target="upload">
        <input type="file" name="upload_file" />
        <input type="hidden" name="backurl" value="http://www.a.com/receive" /> //注意这里!
        <input type="submit" value="开始上传" />
    </form>
    <iframe name="upload" id="upload" style="display:none"></iframe> //name和id都设置为upload

    提交到后端之后,直接通过302跳转的backurl,将返回结果加到backurl的查询字符串里面。backurl必须是和提交的页面
    是同域的页面。这样iframe里面的页面可以通过window.top.callback(查询字符串参数)来调用父页面的方法,或者读取iframe
    的src中的查询字符串

    3.2 iframe + window.name

    基本原理:设置了window.name的值后,页面刷新或跳转,window.name的值不发生变化。在页面的iframe的src设置为其他域的页面,
    使用window.name赋值,然后跳转回和当前页面同域的一个代理页面,这时,可以从主页面取到iframe的name值,达到跨域传值的目的。

    注意点:

    1. window.name只能传字符串值,大小可达2MB
    2. iframe的src为跨域页面时,是无法读取其name值的

    代码实现:

    function domainData(url, fn)
    {
        var isFirst = true;
        var iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        var loadfn = function(){
            if(isFirst){
                //proxy.html为一个空文件
                iframe.contentWindow.location = 'http://localhost:8000/proxy.html';
                isFirst = false;
            } else {
                fn(iframe.contentWindow.name);
                //获取数据以后销毁这个iframe,释放内存;保证安全
                iframe.contentWindow.document.write('');
                iframe.contentWindow.close();
                document.body.removeChild(iframe);
                iframe.src = '';
                iframe = null;
            }
        };
        iframe.src = url;
        if(iframe.attachEvent){
            //IE8
            iframe.attachEvent('onload', loadfn);
        } else {
            //other
            iframe.onload = loadfn;
        }
            
        document.body.appendChild(iframe);
    }
    //调用
    domainData('http://localhost:8001/data.html', function(data){
        alert(data);
    });

    跨域的data.html页面内容

    <script type="text/javascript">
      window.name = "跨域得到的数据";
    </script>

    具体可参考: http://www.cnblogs.com/SherryIsMe/p/4752332.html
           http://www.cnblogs.com/ibeisha/p/4059397.html

    4. postmessage

    postmessage和iframe配合可以实现向后端跨域post数据,并且将返回值传递给主页面。

    postMessage发送消息

    otherWindow.postMessage(message,targetOrigin);

    otherWindow: 指目标窗口,是一个dom对象,是 window.frames
          属性的成员或者由 window.open 方法创建的窗口

    参数说明:

    message: 是要发送的消息,类型为 String、Object (IE8、9 不支持Object),ie8,9只能发送字符串消息
    targetOrigin: 是限定消息接收范围,一般是目标域名,不限制可使用 ‘*’

    postMessage接收消息

    //message函数中可以对消息的来源和数据的格式进行检查,增强安全性
    var onmessage = function(){
        var data = event.data,
            origin = event.origin;
        // 只获取需要的域,并非所有都可以跨域
        if (event.origin != "need domain") {
            return false;
        }
        // 传输数据类型校验
        if (typeof(data) !== 'object') {
            return false;
        }
        //handle data
    }
    
    if (typeof window.addEventListener != 'undefined') {
        window.addEventListener('message', onmessage, false);
    } else if (typeof window.attachEvent != 'undefined') {
        //for ie8
        window.attachEvent('onmessage', onmessage);
    }

    回调函数第一个参数接收 Event 对象,有三个常用属性:

    data: 消息
    origin: 消息来源地址
    source: 源 DOMWindow 对象

    具体例子

    页面a和页面b跨域通信

    页面1:www.a.com/a.html
    页面2:www.b.com/b.html

    页面代码:www.a.com/a.html

    <iframe id="myIframe" src="http://www.b.com/b.html"></iframe>
    <script>
    //依赖jquery
    var $myIframe = $('#myIframe');
    // 注意:必须是在框架内容加载完成后才能触发 message 事件哦
    $myIframe.on('load', function(){
        var data = {
            act: 'article',  // 自定义的消息类型、行为,用于switch条件判断等。。
            msg: {
                subject: '跨域通信消息收到了有木有~', 
                author: '跨域者'
            }
        };
        // 不限制域名则填写 * 星号, 否则请填写对应域名如 http://www.b.com
        $myIframe[0].contentWindow.postMessage(data, '*');
    });
    
    // 注册消息事件监听,对来自 myIframe 框架的消息进行处理
    window.addEventListener('message', function(e){
        if (e.data.act == 'response') {
            alert(e.data.msg.answer);
        } else {
            alert('未定义的消息: '+ e.data.act);
        }
    }, false);
    </script>

    页面代码:www.b.com/b.html

    <script>
    // 注册消息事件监听,对来自 myIframe 框架的消息进行处理
    window.addEventListener('message', function(e){
        if (e.data.act == 'article') {
            alert(e.data.msg.subject);
            // 向父窗框返回响应结果
            window.parent.postMessage({ 
                act: 'response', 
                msg: {
                    answer: '我接收到啦!'
                }
            }, '*');
        } else {
            alert('未定义的消息: '+ e.data.act);
        }
    }, false);
    </script>

    更详细的例子可以参考:http://blog.csdn.net/qiqingjin/article/details/51326060

    5. 后端代理或nginx代理

    6. 图像ping

    和JSONP一样的技术,只不过是利用img标签。img标签可以发送GET请求,但是无法获得响应数据,只能判断是否接收成功。非常适合追踪用户点击和在线广告曝光次数

    let img = new Image()
    let count = 0;
    img.onload = img.onerror = function () {
      count++;
    }
    //src一设置,就会发送请求
    img.src='https://www.test.com/index?data=test
  • 相关阅读:
    Ruby学习笔记5: 动态web app的建立 (2)
    Ruby学习笔记4: 动态web app的建立
    Ruby学习笔记3:Rendering(渲染)和 Redirect(重定向)
    对互联网垂直社交产品的分析
    测试 | 代码覆盖测试工具 | Eclemma
    Jquery | 基础 | 事件的链式写法
    Jquery | 基础 | html()
    Serervlet | 慕课课程实战 | 编写登录逻辑
    Jquery | 外部插入节点
    Jquery | 基础 | .hover()
  • 原文地址:https://www.cnblogs.com/mengff/p/7533461.html
Copyright © 2020-2023  润新知