js跨域

个人对跨域的一些理解,如有不对,请多多指教。

一.ajax设置Access-Control-Allow-Origin实现跨域访问

通过XHR实现Ajax通信的一个主要限制,来源与跨域安全策略。默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源。

CORS(Cross-Origin Resource Sharing,跨源资源共享)是W3C的一个工作草案,定义了在必须访问跨资源时,浏览器与服务器应该如何沟通。其背后的基本思想是:就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

对于简单的get或post发送的请求,它没有自定义的头部,而主体内容是text/plain。在发送请求时,需要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口号),以便服务器根据这个头部信息来决定是否给予响应。

小例子:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>cors_post</title>
    </head>
    <body>
    <div id="box"></div>
    <script type="text/javascript">
        function createXhr(){
            if(typeof XMLHttpRequest){
                return new XMLHttpRequest();
            }else if(typeof ActiveXObject){
                return new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
        var xhr=createXhr();
        var data={
            name:"liujie",
            age:"23"
        };
        xhr.onreadystatechange=function(){
            if(xhr.readyState==4){
                if(xhr.status==200){
                    //console.log(xhr.responseText);
                    document.getElementById("box").innerHTML=data.name+'---'+data.age;
                }
            }
        }
        xhr.open("post","http://www.abc.com/21code/cors/post.php",true);
        xhr.send(data);
    </script>
    </body>
</html>
请求页面post.php:

<?php
header('content-type:application:json;charset=utf8');
// 指定允许其他域名访问
//header('Access-Control-Allow-Origin:http://www.example.com');
header('Access-Control-Allow-Origin:*');
// 响应类型
header('Access-Control-Allow-Methods:POST');
// 响应头设置
header('Access-Control-Allow-Headers:x-requested-with,content-type');
$res= array(
    'name' => isset($_POST['name'])? $_POST['name'] : '',
    'gender' => isset($_POST['gender'])? $_POST['gender'] : ''
);
echo json_encode($res);
 ?>
通过http://www.example.com访问这个页面,Origin:http://www.example.com

如果服务器认为这个请求可以接收,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回发"*")。

比如:Access-Control-Allow-Origin:http://www.example.com

如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。

二、图像Ping

使用img标签,我们知道,一个网页可以从任何网页中加载图像,不用担心跨域不跨域。

动态创建图像经常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的。来看下面的例子。

<!DOCTYPE html>
<html>
<head>
    <title>Image Ping Example</title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        var img = new Image();
        img.onload = img.onerror = function(){
            alert("Done!");
        };
        img.src = "http://www.example.com/ajax?name=test";
    </script>
</body>
</html>

这里创建了一个Image的实例,然后将onload和onerror事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知。请求从设置src属性那一刻开始,而这个例子在请求中发送了一个name参数。

图像Ping最常用于跟踪用户点页面或动态广告曝光次数。图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本。因此,图像Ping只能用于浏览器与服务器间的单向通信。

三、jsonp实现跨域

JSONPjson+padding(内填充),顾名思义,就是把JSON填充到一个盒子里

原理是:动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。

由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。

优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。

1.在http://example.com/jsonp/目录下有个remote.js文件代码如下:

alert("我是远程服务器上的文件");

在http://www.example.com/jsonp/目录下有个jsonp.html页面代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>jsonp01</title>
    </head>
    <body>
    <script type="text/javascript" src="http://example.com/jsonp/remote.js"></script>
    </body>
</html>

访问jsonp.html页面将会弹出一个提示窗体,显示跨域调用成功。

2.在jsonp02.html页面定义一个函数,然后在远程remote.js中传入数据进行调用

jsonp02.html页面代码如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>jsonp02</title>
    </head>
    <body>
    <script type="text/javascript">
    //现在我们在jsonp02.html页面定义一个函数,然后在远程remote.js中传入数据进行调用。
     var localHandler = function(data){
        alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
    };
    </script>
    <script type="text/javascript" src="http://example.com/jsonp/remote.js"></script>
    </body>
</html>

remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});
访问jsonp.html页面,成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。

3.动态生成script标签

jsonp03.html页面的代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>jsonp03</title>
    </head>
    <body>
    <script type="text/javascript">
     // 得到学生信息查询结果后的回调函数
    var flightHandler = function(data){
        alert('你查询的学生信息结果是:姓名:' + data.name+'--'+ '年龄:' + data.age);
    };
    // 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
    var url = "http://example.com/jsonp/remote.js?num=001&callback=flightHandler";
    // 创建script标签,设置其属性
    var script = document.createElement('script');
    script.src=url;
    // 把script标签加入head,此时调用开始
    document.getElementsByTagName('head')[0].appendChild(script);
    </script>
    </body>
</html>
remote.js文件代码如下:
flightHandler({
    "num": "001",
    "name": "lisi",
    "age": 25
});
调用的url中传递了一个num参数,告诉服务器我要查的是001的学生信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。我们看到,传递给flightHandler函数的是一个json,它描述了学生的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!

四、通过修改document.domain来跨子域

将子域和主域的document.domain设为同一个主域.前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域

主域相同的使用document.domain

假设有一个页面,它的地址是http://www.example.com/abc.html,在这个页面里面有一个iframe,它的src是http://example.com/ab.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西。

这时在页面 http://example.com/ab.html 和http://www.example.com/abc.html中设置相同的document.domain,这样我们就可以通过js访问到iframe中的各种属性和对象了。

http://www.example.com/abc.html页面代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>通过修改document.domain来跨子域</title>
    </head>
    <body>
    <iframe src="http://example.com/ab.html" id="iframe" onload="test()"></iframe>
    <script type="text/javascript">
    document.domain="example.com";//设置成主域
    function test(){
        var iframe=document.getElementById("iframe");
        var win=iframe.contentWindow;
        var doc=win.document;
        var name=win.name;
        alert(win);
        alert(doc);
       alert(name);
    }
    </script>
    </body>
</html>
http://example.com/ab.html页面代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>通过修改document.domain来跨子域</title>
    </head>
    <body>
    <script type="text/javascript">
    document.domain="example.com";//设置成主域
    </script>
    </body>
</html>

五、使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的

http://www.example.com/aaa.html页面代码:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>使用window.name来进行跨域</title>
        <script type="text/javascript">
         function getData(){//iframe载入data.html页面后会执行此函数
        var iframe=document.getElementById("iframe");
        iframe.onload=function(){//这个时候aaa.html与iframe已经是处于同一源了,可以互相访问
            var data=iframe.contentWindow.name;//获取iframe里的window.name,也就是data.html页面给它设置的数据
            alert(data);//成功获取到了data.html里的数据
        };
        iframe.src="a.html";//这里的a.html为随便的一个页面,只要与aaa.html同源就行,目的是让aaa.html能访问到iframe里的东西,设置成about:blank也行
    }
    </script>
    </head>
    <body>
    <iframe style="display:none;" src="http://example.com/data.html" id="iframe" onload="getData()"></iframe>
    </body>
</html>
http://example.com/data.html页面代码:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>使用window.name来进行跨域</title>
    </head>
    <body>
    <script type="text/javascript">
    window.name="我是页面aaa.html想要的数据";
    </script>
    </body>
</html>

六 跨文档消息传递

跨文档消息传送(cross-document messaging),简称XDM,指的是在来自不同域的页面间传递消息。
这个例子就是位于www.example.com域中的页面postmessage.html与位于一个内嵌框架中的www.abc.com域中的页面postmessage2.html通信。

XDM的核心是postMessage()方法,利用这个方法向另外一个地方传递数据。这里另外一个地方指的是包含在当前页面中的<iframe>元素,或者由当前页面弹出的窗口。 postMessage()方法接收两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。第二个参数对保障安全通信非常重要,可以防止浏览器把消息发送到不安全的地方。如果传给postMessage()方法的第二个参数是"*",则表示可以把消息发送给来自任何域的文档。


    接收到跨文档消息传送的消息时,会触发window对象的message事件,这个事件是以异步形式触发的
    要想接收从其他窗口发过来的消息,必须对窗口对象的message事件进行监听

    传给onmessage处理程序的事件对象包含以下三个方面的重要信息:
    data:作为postMessage()方法第一个参数传入的字符串数据,可以通过message事件的data属性获取消息的内容
    origin:发送消息的文档所在的域,可以通过message事件的origin属性获取消息的发送源
    source:发送消息的文档的window对象的代理,可以通过message事件的source属性可以获取消息发送源的窗口对象

Demo

postmessage.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨文档消息传递</title>
</head>
<body>
    <div id="content"></div>
    <div>
        <input type="text" size="40" id="msg" placeholder="请输入内容">
        <input type="button" id="btn" value="Send to iframe">
    </div>
    <iframe id="inner" width="450" height="300" src="http://www.abc.com/HTML5/postMessage/postmessage2.html"></iframe>
    <script type="text/javascript" src="../EventUtil.js"></script>
    <script type="text/javascript">
    EventUtil.addHandler(window,'message',function(event){
        var content=document.getElementById("content");
        content.innerHTML="iframe said:"+event.data;
        //event.source.postMessage("1页面收到消息","http://www.abc.com");
    });
    EventUtil.addHandler(document.getElementById("btn"),"click",function(event){
        //msg中存放的是要发送给另一个页面的消息
        var msg=document.getElementById("msg").value;
        innerWindow=document.getElementById("inner").contentWindow;

        if(innerWindow.postMessage){
            //*表示可以把消息发送给来自任何域的文档
            innerWindow.postMessage(msg,"http://www.abc.com/HTML5/postMessage/postmessage2.html");
        }else{
            alert("Your browser doesn't support Cross Document Messaging.");
        }
    });
    </script>
</body>
</html>
postmessage2.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨文档消息传递</title>
</head>
<body>
<div id="content"></div>
<div>
    <input type="text" id="msg" size="40" placeholder="请输入内容">
    <input type="button" id="btn" value="Send to parent">
</div>
<script type="text/javascript" src="../EventUtil.js"></script>
<script type="text/javascript">
EventUtil.addHandler(window,'message',function(event){
    var content=document.getElementById("content");
    content.innerHTML="parent said:"+event.data;
    //event.source.postMessage("2页面收到消息","http://www.example.com");
});
EventUtil.addHandler(document.getElementById("btn"),'click',function(event){
    var msg=document.getElementById("msg").value;
    /*跨文档消息传送的核心是postMessage()方法
    使用window对象的postMessage()方法向其他窗口发送消息
    这个方法接收两个参数:第一个参数为所发送的消息文本;第二个参数为接收消息的对象窗口的URL地址
    可以在URL地址字符串中使用通配符"*"指定全部地址,但是建议使用准确的URL地址
    */
   //console.log(parent);// Window
    if(parent.postMessage){
        parent.postMessage(msg,"http://www.example.com/HTML5/postMessage/postmessage.html");/*使用通配符"*"指定全部地址*/
    }else{
        alert("Your browser doesn't support Cross Document Messaging.");
    }
});
</script>
</body>
</html>

注: EventUtil是封装好的一个js文件,里面定义了给元素绑定事件的一些函数。   

七. Comet

Ajax是一种页面向服务器请求数据的技术,Comet是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时的被推送到页面上,非常适合处理体育比赛和股票报价。

有两种实现Comet的方式:长轮询和流。长轮询是传统轮询(即短轮询)的一个翻版,即浏览器定时向服务器发送请求,看看有没有数据更新。

短轮询:浏览器定时向服务器发送请求,看看有没有数据更新

js跨域_第1张图片

长轮询把传统轮询颠倒了一下,页面发送一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据后,浏览器关闭连接,随即又发起一个到服务器的新请求。这个过程在页面打开期间一直不断持续。

长轮询:页面发送一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据后,浏览器关闭连接,随即又发起一个到服务器的新请求。

js跨域_第2张图片

无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用XHR对象和setTimeout()就能实现,你要做的就是决定什么时候发送请求。


第二种流行的Comet方式是HTTP流。流不同于上述的两种轮询,它在页面的整个生命周期中只使用一个HTTP连接。具体来说就是浏览器向服务器发送一个请求,然后服务器保持连接打开,然后周期性的向浏览器发送数据。下面这段php脚本就是采用流实现的服务器中的常见方式:

streaming.php:

<?php
$i=0;
while(true){
echo "Number is $i";//输出一些数据,然后立即刷新输出缓存
flush();
sleep(10);//等几秒
$i++;
}
?>

通过侦听readystatechange事件及检测readyState的值是否为3,就可以利用XHR对象实现HTTP流。随着不断从服务器接收数据,readyState的值就会周期性地变为3,当readyState值变为3时,responseText属性中就会保存接收到的所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。

只要readystatechange事件发生,而且readyState值为3,就对responseText进行分割以取得最新数据。这里的received变量用于记录已经处理了多少个字符,每次readyState值为3时都递增。然后,通过progress回调函数来处理传入的新数据。而当readyState值为4时,则执行finished回调函数,传入响应返回的全部内容。

<!DOCTYPE html>
<html>
<head>
    <title>HTTP Streaming Example</title>
    <meta charset="utf-8">
</head>
<body>
    <script>
        function createStreamingClient(url, progress, finished){
            //三个参数分别是:要连接的URL、在接收到数据时调用的函数以及关闭连接时调用的函数
            var xhr = new XMLHttpRequest(),
                received = 0;

            xhr.open("get", url, true);
            xhr.onreadystatechange = function(){
                var result;
                if (xhr.readyState == 3){
                    //只取得最新数据并调整计数器
                    result = xhr.responseText.substring(received);
                    received += result.length;

                    //调用progress回调函数
                    progress(result);

                } else if (xhr.readyState == 4){
                    //finished函数是用来关闭连接的
                    finished(xhr.responseText);
                }
            };
            xhr.send(null);
            return xhr;
        }

        var client = createStreamingClient("streaming.php", function(data){
                        alert("Received: " + data);
                     }, function(data){
                        alert("Done!");
                     });

    </script>
</body>
</html>

八.服务器发送事件

SSE(Server-Sent Events,服务器发送事件)是围绕只读Comet交互推出的API或者模式。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的Javascript API能解析的格式输出。SSE支持短轮询、长轮询和HTTP流,而且能在断开连接时自动确定何时重新连接。

1.SSE API
SSE是为javascript api与其他传递消息的javascript api很相似。要预定新的事件流,要创建新的EventSource对象,并传入一个入口点:
var source=new EventSource("myevents.php");

注意:
要传入的URL必须与创建对象的页面同源。EventSource的实例有一个readyState属性,值为0表示正连接到服务器,值为1表示打开了连接,值为2表示关闭连接。另外还有以下三个事件:
open:在建立连接时触发
message:在从服务器接收到新事件时触发
error:在无法建立连接时触发
服务器返回的数据以字符串的格式保存在event.data中。
默认情况下,EventSource对象会保存于服务器的活动连接。如果连接断开,还会重新连接。这就意味着SSE适合长轮询和HTTP流。如果想强制立即断开连接并且不再重新连接,可以调用close()方法。


2.事件流
所谓的服务器事件会通过一个持久的HTTP响应发送,这个响应的MIME类型为text/event-stream。响应的格式是纯文本。

九.Web Sockets

web sockets使用了自定义的协议。未加密的协议是ws://;加密的协议是wss://。
要创建web sockets,先实例一个 WebSocket对象并传入要连接的URL:
var sockets=new  WebSockets("ws://www.example.com/server.php");
注意,必须给WebSockets构造函数传入绝对的URL。同源策略对它不适用,因此可以通过它打开到任何站点的连接。

关闭WebSocket连接:
sockets.close();   //关闭
发送和接收数据:
sockets.send("Hello word");//可以发送字符串,json格式的字符串
sockets的事件有onmessage:当服务器向客户端发来消息时,WebSocket对象就会触发message事件,这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中。
Socket.message=function(event){
var data=event.data;
//处理数据
 }
其他事件:
open:在成功建立连接时触发;
error:在发生错误时触发,连接不能持续;
close:在连接关闭时触发。close事件的event对象有三个额外的属性:wasClean,code,reason。其中,wasClean是一个布尔值,表示连接是否已经明确地关闭;code是服务器返回的数值状态码,而reason是一个字符串,包含服务器发回的消息。

Web Socket协议不同于HTTP,所以现有的服务器不能用Web Socket通信。SSE倒是通过常规HTTP通信,因此现有服务器就可以满足需求。要是双向的通信,Web Sockets更好一些。在不能使用Web Sockets的情况下,组合XHR+SSE也能实现双向通信。


你可能感兴趣的:(JavaScript,跨域)