越努力,越幸運!
個人對跨域的一些理解,如有不對,請多多指教。
在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實現跨域
JSONP
:json+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.伺服器可以主動向客戶端推送訊息,而不用客戶端去查詢