跨域与jsonp

同源策略

只有满足同源的脚本才可以获取资源,这样确实保证了网络上的安全性,但是限制了资源之间的互相利用,比如ajax,ajax是通过url来获取数据,同样也会受到同源策略的限制。

解决跨域问题的方法

  1. flash。很久之前的方法,目前基本弃用;
  2. 服务器代理中转。服务器不会受到同源策略的限制。
  3. iframe+document.domain。这种方法只能针对主域名相同的子域之间。
  4. iframe+location.hash。
  5. iframe+window.name。
  6. HTML5 postMessage方法。
  7. jsonp。这个是现在主要使用的。

document.domain+iframe

只有在主域相同而子域不同的情况下这种方式才可以使用。

子域:
以百度为例,百度的主网页是www.baidu.com,而下面还有很多像zhidao.baidu.com、news.baidu.com等,这些都可称为www.baidu.com这个主域下的子域。

具体做法:
具体的做法是:在http://www.a.com/a.htmlhttp://script.a.com/b.html两个文件中分别加上document.domain=”a.com”,然后通过a.html创建一个iframe,然后去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。

 // a.html
<script type="text/javascript">
    document.domain = 'a.com';
    var ifr = document.createElement('iframe');
    ifr.src = 'http://script.a.com/b.html';
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    ifr.onload = function(){
        var doc = ifr.contentDocument || ifr.contentWindow.document;
        // 在这里操纵b.html
        alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
    };
</script>
 // b.html
<script type="text/javascript">
    document.domain = 'a.com';
</script>

备注:页面的domain默认等于window.location.hostname。主域名是不带有www的域名,例如baidu.com,doamin只能设置为主域名。

缺点
1. 安全性。当一个站点被攻击后,另一个站点就会引起安全漏洞。
2. 操作繁琐。如果一个页面中引入多个iframe,要想能够操作所有的iframe,必须都得设置相同的domain。

iframe+location.hash

原理
利用location.hash来进行传值。在一个url:http://www.baidu.com#hello;其中的#hello就是通过location.hash来设置的,改变hash值并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然,这个东西既然放到了url里面,自然就会有长度的限制。

假设域名a.com下的文件demo1.html要和b.com下的demo2之间传递信息,demo1.thml首先要自动创建一个隐藏的iframe,iframe的src指向b.com下的这个demo2.html网页,这个时候,hash值就可以作为参数传递了。

demo2.html响应请求之后,通过修改demo1.html的hash值来传递数据。

需要注意:当两个页面不在同一个域名下的时候,IE、chrome不允许修改parent.location.hash的值,所以要借助a.com域名下的一个代理iframe。Firefox中可以修改。

 // demo1.html
<button id="btn">Click me to get message</button>
<script type="text/javascript">
    var btn = document.getElementById('btn'),
        ifr = document.createElement('iframe');
    ifr.src = 'http://localhost/2.html';
    btn.onclick = function() {
         location.hash && console.log(location.hash);
    };
    document.body.appendChild(ifr);
</script>
 // demo2.html
<script type="text/javascript">
    try {
        parent.location.hash = 'hello?';
    }catch (e) {
        // ie、chrome的安全机制无法修改parent.location.hash,
               // 所以要利用一个中间的和1.html同域下的代理iframe
               location.hash = "helllo?";
               var tempIfr = document.createElement('iframe');
            tempIfr.style.display = 'none';
            tempIfr.src = "temp.html";
                document.body.appendChild(tempIfr);
    }
</script>
 // temp.html 可在本地服务器模拟
<script type="text/javascript">
    //这里的parent是2.html,也就是我们要传递数据的页面
    //parent.parent是主页面
    parent.parent.location.hash = parent.location.hash;
</script>

缺点:
数据直接暴露在url中,数据的长度和类型都有限制。

iframe+window.name

原理:
与location.hash方法差不多,也是主页面有一个iframe,然后通过修改子页面的window.name来让主页面获取到这个值。

需要三个页面
1.htmll 主页面;

temp.html 代理页面,通常没有任何html内容,要和主页面在同一个域名下;

2.html 数据页面;

在主页面1.html下创建一个iframe,把它的src指向数据页面2.thml,数据页面会把数据附加到这个iframe的window.name上。

 // 1.html主页面
<button id="btn">Click me to get message</button>
<script type="text/javascript">
    var btn = document.getElementById('btn'),
        hasGetData = false;
        ifr = document.createElement('iframe');
    ifr.src = 'http://localhost/2.html';
    btn.onclick = function() {
        if (hasGetData) {
            console.log(ifr.contentWindow.name);
            document.body.removeChild(ifr);
        }else {
            ifr.contentWindow.location = 'http://localhost/temp.html';
            hasGetData = true;
        }
    };
    document.body.appendChild(ifr);
</script>
 2.html
<script type="text/javascript">
    window.name = 'hello!';
</script>

在主页面中,最开始让iframe的src指向了2.html这个页面,获取了它里面的window.name的值,但是这个时候如果在真的服务器上的话,因为域名不同所以无法直接传值。

当第一次点击了按钮之后,把iframe的locatin值换成了和1.html同域名的temp.html,这两个同域名的网页之间可以互相访问,而且此时我们也没有改变刚才的name的值,这样就把两个不同域名的数据通过一个中间页面的形式传递过来了。

获取数据以后销毁这个iframe,释放内容,同时保证安全性。

总结:利用iframe的src属性实现让外域转向本地域,这样数据就由iframe的window.name从外域传递到了本地域。这样既巧妙地绕过了浏览器的跨域问题,又能保证安全性。

缺点:通信的数据量很小。

HTML5 postMessage方法

H5中有一个功能就是跨文档消息传输。主流浏览器的较高版本包括IE8和IE8以上都实现了这个功能。而且facebook已经使用了这个功能,用postMessage支持web的实时消息传递。

getMessageHTML.postMessage(message, targetOrigin);

其中getMessageHTML是我们对于要接受信息的页面的引用,可以是iframe的contentWindow属性、window.open的返回值、通过name或下标从window.frames取到的值。

message是我们所要发送的信息,字符串类型。

targetOrigin是用于限制getMessageHTML的,当填“*”的时候是不做限制。

 // 1.html
<iframe id="ifr" src="2.html"></iframe>
<button id="btn">Click me to change color</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function() {
      var ifr = document.getElementById('ifr');
      ifr.contentWindow.postMessage('black', '*');
};
</script>
 // 2.html
<div id="div"></div>
<script type="text/javascript">
    var div = document.getElementById('div');
    window.addEventListener('message', function(event){
        div.style.backgroundColor = event.data;
    }, false);
</script>

点击click按钮之后,iframe中那个红色的方块就会变成黑色,这样就完成了一个最基本的实时通信功能。不过这里注意本来的2.html里面的那个div的颜色并没有改变。

服务器代理中转

因为服务器不受同源策略的限制,所以只要把资源全部放到自己的服务器上,然后让这个服务器上的网页获取这些资源就可以了。

重点:jsonp

原理:web页面上script引入js文件不受跨域的影响,不仅如此,凡是有src属性的标签都不受同源策略的影响。

通过这个特性,可以把资源直接放到script标签的src里面,这样就把数据放到了服务器上,并且是使用json的形式。(js可以轻松操作json数据)

但是由于无法监控script的src的加载状态,不知道数据有没有获取完成,所以要事先定义好处理函数。

<script type="text/javascript">
    function success (data) {
        console.log(data.name + ' ' + data.age);
    }
</script>

然后在数据里面开头写上这个函数名,引入进来之后等到文件全部加载完毕,刚好就是调用已经定义好的函数,并且参数就是后面的那一串数据。

在这之前,先创建一个txt文件,里面写上:

// txt 文件
success({“name”: “Mandy”, “age”: 18})

然后在html文件中写上:

<script type="text/javascript">
    function success (data) {
        console.log(data.name + ' ' + data.age);
    }
</script>
<script type="text/javascript" src="./test.txt"></script>
// 最后打印Mandy 18

在实际开发中,script标签的src属性的格式是“url+参数”的形式,比如“url?cb=doJSON”,这里的cb是和后台人员协商好的类似形参的东西,是一个留给我们写处理函数的接口,后面的doJSON就是我们事先定义好的处理函数。

模拟百度搜索栏

检测百度网页的资源文件,发现当在输入栏中输入文字的时候,下面出来的那些关键字信息都是存放在http://suggestion.baidu.com/su?wd=输入值&cb=处理函数,这个url里面的,而且主要是在这个对象里面的s属性上。

所以我们可以直接在input标签的keyup上面绑定这样一个事件:

在input标签的keyup上面绑定事件:


window.onload = function(){
    var $Input = document.getElementById('q');
    var $Ul = document.getElementById('ul1');
    $Input.onkeyup = function(){
        if(this.value != ''){
            var $Script = document.createElement('script');
            $Script.src = 'http://suggestion.baidu.com/su?wd=' + this.value + '&cb=doJSON';
            document.body.appendChild($Script);
        }else{
            $Ul.style.display = 'none';
        }
    }
}

处理函数:
点击每一个显示出来的关键字信息的时候,都会跳转到一个新页面,而这些新页面的链接都是https://www.baidu.com/s?wd=刚才的输出值出来的对象的s属性的值,因此处理函数可以写成:


function doJSON(data){
    var $Ul = document.getElementById('ul');
    var html = '';
    if (data.s.length){
        $Ul.style.display = 'block';
        for(var i = 0; i < data.s.length; i++){
            html += '<li><a href="https://www.baidu.com/s?wd=' + data.s[i] + '" target="_blank">' + data.s[i] + '</a></li>'
                } 
               $Ul.innerHTML = html; 
        }else{ 
               $Ul.style.display = 'none'; 
        }
}

一个简陋的百度搜索栏就完成了。

本文内容转自http://www.flyingliu.com/?p=312

猜你喜欢

转载自blog.csdn.net/Mandyucan/article/details/81029591
今日推荐