跨域总结

跨域定义

协议、端口号、域名有一个不同就是跨域。
主域名相同,子域名不同也是跨域,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 全类型

跨域解决方法

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

 

posted @ 2017-09-16 23:28  全玉  阅读(373)  评论(0编辑  收藏  举报