1. 程式人生 > >越努力,越幸運!

越努力,越幸運!

個人對跨域的一些理解,如有不對,請多多指教。

JavaScript中,有一個很重要的安全性限制,被稱為Same-Origin Policy”(同源策略)。這一策略指定:JavaScript只能訪問與包含它的文件在同一域下的內容。所謂同源是指,域名(host),協議(protocol),埠(port)相同。

URL 說明 是否跨域
http://www.abc.com/a.js
http://www.abc.com/b.js
同一域名下
http://www.abc.com/test/a.js
http://www.abc.com/lib/b.js
同一域名下不同資料夾
http://www.abc.com:80/a.js
http://www.abc.com/b.js
同一域名,不同埠號
http://www.abc.com/a.js
https://www.abc.com/b.js
同一域名,不同協議(http和https)
http://www.abc.com/a.js
http://127.0.0.1/b.js
域名與域名對應IP
http://www.abc.com/a.js
http://abc.com/b.js
http://www.test.com/b.js
不同域名(host)

一.ajax設定Access-Control-Allow-Origin實現跨域訪問

通過XHR實現Ajax通訊的一個主要限制,來源與跨域安全策略。預設情況下,XHR物件只能訪問與包含它的頁面位於同一個域中的資源。

CORS(Cross-Origin Resource Sharing,跨源資源共享)是W3C的一個標準,定義了在必須訪問跨資源時,瀏覽器與伺服器應該如何溝通。它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。其背後的基本思想是:就是使用自定義的HTTP頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。

對於簡單的get或post傳送的請求,它沒有自定義的頭部,而主體內容是text/plain。在傳送請求時,需要給它附加一個額外的Origin頭部,其中包含請求頁面的源資訊(協議、域名和埠號),以便伺服器根據這個頭部資訊來決定是否給予響應。

CORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。
整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊(origin頭),有時還會多出一次附加的請求,但使用者不會有感覺。
因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊

小例子:

<!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');
$res= array(
    'name' => isset($_POST['name'])? $_POST['name'] : '',
    'gender' => isset($_POST['gender'])? $_POST['gender'] : ''
);
echo json_encode($res);
 ?>
<img src="https://img-blog.csdn.net/20160809155156139?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

通過http://www.example.com訪問這個頁面,Origin:http://www.example.com

如果伺服器認為這個請求可以接收,就在Access-Control-Allow-Origin頭部中回發相同的源資訊(如果是公共資源,可以回發"*")。

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

如果沒有這個頭部,或者有這個頭部但源資訊不匹配,瀏覽器就會駁回請求。

cors基本流程:

1.瀏覽器對於AJAX請求會自動在頭資訊之中,新增一個Origin欄位,該欄位用來說明,本次請求來自哪個源(協議 + 域名 + 埠)。伺服器根據這個值,決定是否同意這次請求。 2.如果Origin指定的域名在許可範圍內,伺服器返回的響應,並在響應頭的Access-Control-Allow-Origin頭部中會發相同的源資訊。

與CORS請求相關的欄位:

(1)Access-Control-Allow-Origin
該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Methods
該欄位必需,它的值是逗號分隔的一個字串,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
(3)Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字串,表明伺服器支援的所有頭資訊欄位,不限於瀏覽器在"預檢"中請求的欄位。
(4)Access-Control-Max-Age
該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許快取該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
(5)Access-Control-Allow-Credentials
該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true時,Cookie就會包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。

(6)Access-Control-Expose-Headers
該欄位可選。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。例如:getResponseHeader('FooBar')可以返回FooBar欄位的值。

withCredentials 屬性

CORS請求預設不傳送Cookie和HTTP認證資訊。如果要把Cookie發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials欄位。
Access-Control-Allow-Credentials: true
另一方面,開發者必須在AJAX請求中開啟withCredentials屬性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否則,即使伺服器同意傳送Cookie,瀏覽器也不會發送。 但是,如果省略withCredentials設定,有的瀏覽器還是會一起傳送Cookie。這時,可以顯式關閉withCredentials。
xhr.withCredentials = false;
需要注意的是,如果要傳送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用伺服器域名設定的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁程式碼中的document.cookie也無法讀取伺服器域名下的Cookie。

與jsonp的區別:

CORS與JSONP的使用目的相同,但是比JSONP更強大。

JSONP只支援GET請求,CORS支援所有型別的HTTP請求。JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料。

二、影象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資料作為引數傳入。

首先在客戶端註冊一個callback, 然後把callback的名字傳給伺服器。此時,伺服器先生成 json 資料。然後以 javascript 語法的方式,生成一個function , function 名字就是傳遞上來的引數 jsonp.最後將 json 資料直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文件,返回給客戶端。客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文件,此時資料作為引數,傳入到了客戶端預先定義好的 callback 函式裡.(動態執行回撥函式)

由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、埠)的資源,為了實現跨域請求,可以通過script標籤實現跨域請求,然後在服務端輸出JSON資料並執行回撥函式,從而解決了跨域的資料請求。

jsonp允許在伺服器端整合Script 標籤返回至客戶端,通過javascript回撥函式的形式實現跨域訪問。它可以繞過同源策略的方法,即通過使用JSON與script標籤相結合的方法,從伺服器直接返回可執行的javascript函式呼叫或者javascript物件。

優點是相容性好,簡單易用,支援瀏覽器與伺服器雙向通訊。缺點是隻支援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的執行全過程順利完成!

jQuery對jsonp的實現

原理是一樣的,只不過我們不需要手動的插入script標籤以及定義回掉函式。jQuery會自動生成一個全域性函式來替換callback=?中的問號,之後獲取到資料後又會自動銷燬,實際上就是起一個臨時代理函式的作用。

jquery對jsonp的支援:
兩種方式:getJSON()方法和ajax指定返回jsonp型別
使用getJSON()方法時,必須指定callback=?,表示使用使用jsonp的方式來發起請求

$.getJSON方法會自動判斷是否跨域,不跨域的話,就呼叫普通的ajax方法;跨域的話,則會以非同步載入js檔案的形式來呼叫jsonp的回撥函式。

getJSON()方法:Demo

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>jsonp</title>
</head>
<body>
<input type="button" value="jsonp跨域" onclick="testJsonp()">
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript">
	function testJsonp(){
		$.getJSON('http://b.test.com/mywork/kuayu/jsonp/jsopn.php?callback=?',
			function(data){
			console.log(data);
		})
	}
</script>
</body>
</html>

jsonp.php
<?php
$jsonp = $_REQUEST["callback"];
$str = '{"name":"lisi","age":23}';
$str = $jsonp . "(" .$str.")";
echo $str;
?>
ajax指定返回jsonp型別:Demo
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>jsonp</title>
</head>
<body>
<input type="button" value="jsonp跨域" onclick="testJsonp()">
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript">
/*
這裡jQuery將自動把URL裡的回撥函式,例如"url?callback=?"中的後一個"?"替換為正確的函式名,以執行回撥函式
 */
	function testJsonp(){
		$.ajax('http://b.test.com/mywork/kuayu/jsonp/jsopn.php?callback=?',
			{dataType:'jsonp',
		}).done(function(data){
			console.log(data);
		});
	}
</script>
</body>
</html>

$.ajax()跨域原理分析
由於javascript的安全限制“同源策略”,所以我們無法使用XMLHttpRequest直接請求別的域名下的資源。不過擁有src屬性和href屬性的<script>\<img>\<iframe>和<link>\<a>標籤不受同源策略影響。$.ajax()正是應用了動態建立<script>的方式來實現(即:生成<script>標籤,src引入指令碼,然後執行,最後移除<script>標籤)。

四、通過修改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/mywork/kuayu/windowname/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="http://www.example.com/mywork/kuayu/windowname/a.html";//這裡的a.html為隨便的一個頁面,只要與aaa.html同源就行,目的是讓aaa.html能訪問到iframe裡的東西,設定成about:blank也行
    }
    </script>
    </head>
    <body>
    <iframe style="display:none;" src="http://example.com/mywork/kuayu/windowname/data.html" id="iframe" onload="getData()"></iframe>
    </body>
</html>
http://example.com/mywork/kuayu/windowname/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>

整個跨域的流程是這樣的:
先在http://example.com/mywork/kuayu/windowname/data.html中通過一些操作將資料存入window.name中了
而http://www.example.com/mywork/kuayu/windowname/aaa.html想要獲取到window.name的值就需要依靠iframe作為中間代理,首先把iframe的src設定成http://example.com/mywork/kuayu/windowname/data.html,這樣就相當於要獲取iframe的window.name,而要想獲取到iframe中的window.name,就需要把iframe的src設定成當前域的一個頁面地址"http://www.example.com/mywork/kuayu/windowname/aa.html"或者設定成about:blank也行,不然根據前面講的同源策略,window.name.html是不能訪問到iframe裡的window.name屬性的。

六 跨文件訊息傳遞

跨文件訊息傳送(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的方式:長輪詢和流。長輪詢是傳統輪詢(即短輪詢)的一個翻版,即瀏覽器定時向伺服器傳送請求,看看有沒有資料更新。

短輪詢:瀏覽器定時向伺服器傳送請求,看看有沒有資料更新

短輪詢是一種從伺服器拉取資料的工作模式。設定一個定時器,定時詢問伺服器是否有資訊,每次建立連線傳輸資料之後,連結會關閉

短輪詢模式:建立連線——資料傳輸——關閉連線...建立連線——資料傳輸——關閉連線


長輪詢把傳統輪詢顛倒了一下,頁面傳送一個到伺服器的請求,然後伺服器一直保持連線開啟,直到有資料可傳送。傳送完資料後,瀏覽器關閉連線,隨即又發起一個到伺服器的新請求。這個過程在頁面開啟期間一直不斷持續。

長輪詢:頁面傳送一個到伺服器的請求,然後伺服器一直保持連線開啟,直到有資料可傳送。傳送完資料後,瀏覽器關閉連線,隨即又發起一個到伺服器的新請求。

長輪詢模式:建立連線——(保持連線直到有資料可傳送)...資料傳輸——關閉連線(隨即又向伺服器建立一個連線)

長輪詢與短輪詢的不同:主要在於client和server採取的關閉策略不同。短輪詢在建立連線以後只進行一次資料傳輸就關閉連線,而長輪詢在建立連線以後會進行多次資料資料傳輸直至關閉連線。

無論是短輪詢還是長輪詢,瀏覽器都要在接收資料之前,先發起對伺服器的連線。兩者最大的區別在於伺服器如何傳送資料。短輪詢是伺服器立即傳送響應,無論資料是否有效,而長輪詢是等待發送響應。輪詢的優勢是所有瀏覽器都支援,因為使用XHR物件和setTimeout()就能實現,你要做的就是決定什麼時候傳送請求。

HTTP流

第二種流行的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。響應的格式是純文字。
Demo
index.html
<span style="font-size:14px;"><!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>SSE</title>
	<link rel="stylesheet" href="">
</head>
<body>
<!-- 實現伺服器資料推送,例如股票時時重新整理等 -->
	<script type="text/javascript" src="index.js"></script>
</body>
</html></span>

index.js

var source;
function init(argument){
	source = new EventSource('http://localhost/sse/data.php');
	source.onopen=function(){
		console.log('連線已建立',this.readyState);
	}
	source.onmessage=function(event){
		console.log('從伺服器時時獲取的資料',event.data);
	}
	source.onerror=function(){

	}
}
init();

data.php
<?php
header("Content-Type:text/event-stream;charset=utf-8");
header("Access-Control-Allow-Origin:http://127.0.0.1/");
echo "data:現在北京時間是".date('H:i:s')."\r\n\r\n";
 ?>

九.Web Sockets

web sockets使用了自定義的協議。未加密的協議是ws://;加密的協議是wss://。 注意,必須給WebSockets建構函式傳入絕對的URL。同源策略對它不適用,因此可以通過它開啟到任何站點的連線。


sockets.close();   //關閉
傳送和接收資料:
sockets.send("Hello word");//可以傳送字串,json格式的字串
sockets的事件有onmessage:當伺服器向客戶端發來訊息時,WebSocket物件就會觸發message事件,這個message事件與其他傳遞訊息的協議類似,也是把返回的資料儲存在event.data屬性中。
Socket.onmessage=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也能實現雙向通訊。
WebSocket 協議是html5引入的一種新的協議,其目的在於實現了瀏覽器與伺服器全雙工通訊。看了上面連結的同學肯定對過去怎麼低效率高消耗(輪詢或comet)的做此事已經有所瞭解了,而在websocket API,瀏覽器和伺服器只需要要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。同時這麼做有兩個好處:
1.通訊傳輸位元組減少:比起以前使用http傳輸資料,websocket傳輸的額外資訊很少
2.伺服器可以主動向客戶端推送訊息,而不用客戶端去查詢