1、XMLHttpRequest 對象
- 在瀏覽器中創(chuàng)建XHR 對象
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
//兼容IE7以下瀏覽器
if (typeof arguments.callee.activeXString != "string"){
var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳過
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
var xhr = createXHR();
1.1 XHR 的用法
- 在使用XHR 對象時,要調用的第一個方法是open(),它接受3個參數(shù):要發(fā)送的請求的類型("get"、"post"等)、請求的URL 和表示是否異步發(fā)送請求的布爾值。
- 調用open()方法并不會真正發(fā)送請求,而只是啟動一個請求以備發(fā)送。要發(fā)送特定的請求,需要調用send(),它接受一個參數(shù),即要作為請求主體發(fā)送的數(shù)據(jù)。
- 如果不需要通過請求主體發(fā)送數(shù)據(jù),則必須傳入null,因為這個參數(shù)對有些瀏覽器來說是必需的。
xhr.open("get", "example.txt", false);
xhr.send(null);
- 由于請求是同步的,JavaScript代碼會等到服務器響應之后再繼續(xù)執(zhí)行。在收到響應后,響應的數(shù)據(jù)會自動填充XHR 對象的屬性。
(1)responseText:作為響應主體被返回的文本。
(2)responseXML:如果響應的內容類型是“text/xml”或者“application/xml”,這個屬性將保存包含響應數(shù)據(jù)的XML DOM 文檔。
(3)status:響應的HTTP狀態(tài)
(4)statusText:HTTP 狀態(tài)的說明
- 一般來說,可以將HTTP狀態(tài)代碼為200 作為成功的標志。
- 狀態(tài)代碼為304 表示請求的資源并沒有被修改。
xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
- 發(fā)送異步請求時,可以檢測XHR對象的readyState屬性,該屬性表示請求/響應過程的當前活動階段。這個屬性可取的值如下。
- 0:未初始化。尚未調用open()方法。
- 1:啟動。已經(jīng)調用open()方法,但尚未調用send()方法。
- 2:發(fā)送。已經(jīng)調用send()方法,但尚未收到響應。
- 3:接收。已經(jīng)接收到部分響應數(shù)據(jù)。
- 4:完成。已經(jīng)接收到全部響應數(shù)據(jù),而且已經(jīng)可以在客戶端使用了。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
- 在接收到響應之前還可以調用abort()方法來取消異步請求。
xhr.abort();
- 調用這個方法后,XHR對象會停止觸發(fā)事件,而且也不再允許訪問任何與響應有關的對象屬性。
- 在終止請求之后,還應該對XHR對象進行解引用操作。由于內存原因,不建議重用XHR 對象。
1.2 HTTP頭部信息
- 每個HTTP 請求和響應都會帶有相應的頭部信息。XHR對象也提供了操作這兩種頭部信息的方法。
- 默認情況下,在發(fā)送XHR請求的同時,還會發(fā)出下列頭部信息。
- Accept:瀏覽器能夠處理的內容類型。
- Accept-Charset:瀏覽器能夠顯示的字符集。
- Accept-Encoding:瀏覽器能夠處理的壓縮編碼。
- Accept-Language:瀏覽器當前設置的語音。
- Connection:瀏覽器與服務器之間連接的類型。
- Cookie:當前頁面設置的任何Cookie。
- Host:發(fā)出請求的頁面所在的域。
- Referer:發(fā)出請求的頁面的URI。
- User-Agent:瀏覽器的用戶代理字符串。
- 使用setRequestHeader()方法可以設置自定義的請求頭部信息。這個方法接受兩個參數(shù):頭部字段的名稱和頭部字段的值。
- 要成功發(fā)送請求頭部信息,必須在調用open()方法之后且調用send()方法之前調用setRequestHeader()
var xhr = createXHR();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get","example.php",true);
xhr.setRequestHeader("MyHeader","MyValue");
xhr.send(null);
- 調用XHR 對象的getResponseHeader()方法并傳入頭部字段名稱,可以取得相應的響應頭部信息。
- 調用getAllResponseHeaders()方法則可以取得一個包含所有頭部信息的長字符串。
var myHeader = xhr.getResponseHeader("MyHeader");
var allHeaders = xhr.getAllResponseHeaders();
1.3 GET 請求
- GET 是最常見的請求類型,最常用于向服務器查詢某些信息。必要時,可以將查詢字符串參數(shù)追加到URL 的末尾,以便將信息發(fā)送給服務器。
- 對XHR 而言,位于傳入open()方法的URL末尾的查詢字符串必須經(jīng)過正確的編碼才行。
- 查詢字符串中每個參數(shù)的名稱和值都必須使用encodeURIComponent()進行編碼,然后才能放到URL的末尾;而且所有名-值對兒都必須由和號(&)分隔。
xhr.open("get", "example.php?name1=value1&name2=value2", true);
//向現(xiàn)有URL 的末尾添加查詢字符串參數(shù)
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
var url = "example.php";
//添加參數(shù)
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化請求
xhr.open("get", url, false);
1.4 POST 請求
- POST請求通常用于向服務器發(fā)送應該被保存的數(shù)據(jù)。POST請求應該把數(shù)據(jù)作為請求的主體提交,主體可以包含非常多的數(shù)據(jù),而且格式不限。
- 可以使用XHR 來模仿表單提交:首先將Content-Type頭部信息設置為application/x-www-form-urlencoded,也就是表單提交時的內容類型,其次是以適當?shù)母袷絼?chuàng)建一個字符串。
/**以查詢字符串的格式輸出序列化之后的字符串**/
function serialize(form){
var parts = [],field = null,i,len,j,optLen,option,optValue;
for (i=0, len=form.elements.length; i < len; i++){
field = form.elements[i];
switch(field.type){
case "select-one":
case "select-multiple":
if (field.name.length){
for (j=0, optLen = field.options.length; j < optLen; j++){
option = field.options[j];
if (option.selected){
optValue = "";
if (option.hasAttribute){
optValue = (option.hasAttribute("value") ?
option.value : option.text);
} else {
optValue = (option.attributes["value"].specified ?
option.value : option.text);
}
parts.push(encodeURIComponent(field.name) + "=" +
encodeURIComponent(optValue));
}
}
}
break;
case undefined: //字段集
case "file": //文件輸入
case "submit": //提交按鈕
case "reset": //重置按鈕
case "button": //自定義按鈕
break;
case "radio": //單選按鈕
case "checkbox": //復選框
if (!field.checked){
break;
}
/* 執(zhí)行默認操作 */
default:
//不包含沒有名字的表單字段
if (field.name.length){
parts.push(encodeURIComponent(field.name) + "=" +
encodeURIComponent(field.value));
}
}
}
return parts.join("&");
}
function submitData(){
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300)
|| xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form));
}
- 與GET 請求相比,POST 請求消耗的資源會更多一些。從性能角度來看,以發(fā)送
相同的數(shù)據(jù)計,GET 請求的速度最多可達到POST 請求的兩倍。
2、XMLHttpRequest 2 級
- 并非所有瀏覽器都完整地實現(xiàn)了XMLHttpRequest2級規(guī)范,但所有瀏覽器都實現(xiàn)了它規(guī)定的部分內容。
2.1 formData
- XMLHttpRequest 2 級定義了FormData類型。FormData為序列化表單以及創(chuàng)建與表單格式相同的數(shù)據(jù)(用于通過XHR 傳輸)提供了便利。
var data = new FormData();
data.append("name", "Nicholas");
- 這個append()方法接受兩個參數(shù):鍵和值。分別對應表單字段名和字段中包含的值。
- 通過formData構造函數(shù)傳入表單元素,也可以用表單元素的數(shù)據(jù)預先向其中填入鍵值對。
var data = new FormData(document.forms[0]);
- 創(chuàng)建了FormData 的實例后,可以將它直接傳給XHR 的send()方法。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("post","postexample.php", true);
var form = document.getElementById("user-info");
xhr.send(new FormData(form));
- 使用FormData 的方便之處體現(xiàn)在不必明確地在XHR 對象上設置請求頭部。XHR 對象能夠識別傳入的數(shù)據(jù)類型是FormData 的實例,并配置適當?shù)念^部信息。
- 支持FormData 的瀏覽器有Firefox 4+、Safari 5+、Chrome 和Android3+版WebKit。
2.2 超時設定
- IE8 為XHR 對象添加了一個timeout屬性,表示請求在等待響應多少毫秒之后就終止。在給timeout設置一個數(shù)值后,如果在規(guī)定的時間內瀏覽器還沒有接收到響應,那么就會觸發(fā)timeout 事件,進而會調用ontimeout 事件處理程序。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
try {
if ((xhr.status >= 200 && xhr.status < 300)
|| xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} catch (ex){
//假設由ontimeout 事件處理程序處理
}
}
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //將超時設置為1 秒鐘(僅適用于IE8+)
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
2.3 overrideMimeType()方法
- Firefox 最早引入了overrideMimeType()方法,用于重寫XHR 響應的MIME 類型。
- 因為返回響應的MIME類型決定了XHR對象如何處理它,所以提供一種方法能夠重寫服務器返回的MIME 類型是很有用的。
var xhr = createXHR();
xhr.open("get", "text.php", true);
//強迫XHR 對象將響應當作XML 而非純文本來處理
xhr.overrideMimeType("text/xml");
xhr.send(null);
- 支持overrideMimeType()方法的瀏覽器有Firefox、Safari 4+、Opera 10.5 和Chrome。
3、進度事件
- 具有6個進度事件
- loadstart:在接收到響應數(shù)據(jù)的第一個字節(jié)時觸發(fā)。
- progress:在接收響應期間持續(xù)不斷的觸發(fā)。
- error:在請求發(fā)生錯誤時觸發(fā)。
- abort:在因為調用abort()方法而終止連接時觸發(fā)。
- load:在接收到完整的響應數(shù)據(jù)時觸發(fā)。
- loadend:在通信完成或者觸發(fā)error、abort或load事件后觸發(fā)。
3.1 load 事件
- Firefox 實現(xiàn)中引入了load事件,用以替代readystatechange事件。響應接收完畢后將觸發(fā)load事件,因此也就沒有必要去檢查readyState 屬性了。
var xhr = createXHR();
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
3.2 progress 事件
- Mozilla 對XHR 的另一個革新是添加了progress事件,這個事件會在瀏覽器接收新數(shù)據(jù)期間周期性地觸發(fā)。
- onprogress 事件處理程序會接收到一個event 對象,其target 屬性是XHR 對象,但
包含著三個額外的屬性:lengthComputable、position 和totalSize。 - lengthComputable是一個表示進度信息是否可用的布爾值,position表示已經(jīng)接收的字節(jié)數(shù),totalSize 表示根據(jù)Content-Length 響應頭部確定的預期字節(jié)數(shù)。
var xhr = createXHR();
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
//每次觸發(fā)progress 事件,都會以新的狀態(tài)信息更新HTML 元素的內容。
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " +
event.totalSize +" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
4、跨源資源共享
通過XHR 實現(xiàn)Ajax 通信的一個主要限制,來源于跨域安全策略。默認情況下,XHR 對象只能訪問與包含它的頁面位于同一個域中的資源。這種安全策略可以預防某些惡意行為。
CORS定義了在必須訪問跨源資源時,瀏覽器與服務器應該怎么溝通。其背后的思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應應該是成功還是失敗。
比如一個簡單的使用GET或POST發(fā)送的請求,它沒有自定義的頭部,而主體內容是text/plain。在發(fā)送該請求時,需要給它附加一個額外的Origin頭部,其中包含請求頁面的源信息(協(xié)議、域名和端口),以便服務器根據(jù)這個頭部信息來決定是否給予響應。
//Origin 頭部的一個示例:
Origin: http://www.nczonline.net
//如果服務器認為這個請求可以接受
Access-Control-Allow-Origin: http://www.nczonline.net
4.1 IE 對CORS 的實現(xiàn)
- 微軟在IE8 中引入了XDR(XDomainRequest)類型。這個對象與XHR類似,但能實現(xiàn)安全可靠的跨域通信。
- XDR 與XHR 有一些不同之處。
- cookie 不會隨請求發(fā)送,也不會隨響應返回。
- 只能設置請求頭部信息中的Content-Type字段。
- 不能訪問響應頭部信息
- 只支持GET和POST請求
- XDR對象的使用方法與XHR對象非常相似。也是創(chuàng)建一個XDomainRequest的實例,調用open()方法,再調用send()方法。但與XHR對象的open()方法不同,XDR對象的open()方法只接收兩個參數(shù):請求的類型和URL。
- 所有XDR 請求都是異步執(zhí)行的,不能用它來創(chuàng)建同步請求。請求返回之后,會觸發(fā)load 事件,響應的數(shù)據(jù)也會保存在responseText 屬性中。
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
- 為支持POST 請求,XDR對象提供了contentType屬性,用來表示發(fā)送數(shù)據(jù)的格式,這個屬性是通過XDR 對象影響頭部信息的唯一方式。
xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");
4.2 其它瀏覽器對CORS的實現(xiàn)
- Firefox 3.5+、Safari 4+、Chrome、iOS 版Safari 和Android 平臺中的WebKit 都通過XMLHttpRequest對象實現(xiàn)了對CORS 的原生支持。
- 要請求位于另一個域中的資源,使用標準的XHR 對象并在open()方法中傳入絕對URL 即可。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);
- 跨域XHR 對象也有一些限制,但為了安全這些限制是必需的。
- 不能使用setRequestHeader()設置自定義頭部。
- 不能發(fā)送和接收cookie。
- 調用getAllResponseHeaders()方法總會返回空字符串。
4.3 Preflighted Reqeusts
- CORS 通過一種叫做PreflightedRequests的透明服務器驗證機制支持開發(fā)人員使用自定義的頭部、GET 或POST 之外的方法,以及不同類型的主體內容。
- 在使用下列高級選項來發(fā)送請求時,就會向服務器發(fā)送一個Preflight請求。這種請求使用OPTIONS 方法,發(fā)送下列頭部。
- Origin:與簡單的請求相同。
- Access-Control-Request-Method:請求自身使用的方法。
- Access-Control-Request-Headers:(可選)自定義的頭部信息,多個頭部以逗號分隔。
//一個帶有自定義頭部NCZ 的使用POST 方法發(fā)送的請求。
Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ
- 發(fā)送這個請求后,服務器可以決定是否允許這種類型的請求。服務器通過在響應中發(fā)送如下頭部與瀏覽器進行溝通。
- Access-Control-Allow-Origin:與簡單的請求相同。
- Access-Control-Allow-Methods:允許的方法,多個方法以逗號分隔。
- Access-Control-Allow-Headers:允許的頭部,多個頭部以逗號分隔。
- Access-Control-Max-Age:應該將這個Preflight請求緩存多長時間(以秒表示)。
- 支持Preflight 請求的瀏覽器包括Firefox 3.5+、Safari 4+和Chrome。IE 10 及更早版本都不支持。
4.4 帶憑據(jù)的請求
- 默認情況下,跨源請求不提供憑據(jù)(cookie、HTTP認證及客戶端SSL證明等)。通過將withCredentials屬性設置為true,可以指定某個請求應該發(fā)送憑據(jù)。如果服務器接受帶憑據(jù)的請求,會用下面的HTTP 頭部來響應。
Access-Control-Allow-Credentials: true
- 如果發(fā)送的是帶憑據(jù)的請求,但服務器的響應中沒有包含這個頭部,那么瀏覽器就不會把響應交給JavaScript.(于是,responseText 中將是空字符串,status 的值為0,而且會調用onerror()事件處
理程序)
4.5 跨瀏覽器的CORS
- 有必要實現(xiàn)一個跨瀏覽器的方案。檢測XHR 是否支持CORS 的最簡單方式,就是檢查
是否存在withCredentials屬性。再結合檢測XDomainRequest對象是否存在,就可以兼顧所有瀏覽器。
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
vxhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get","http://www.somewhere-else.com/page/");
if (request){
request.onload = function(){
//對request.responseText 進行處理
};
request.send();
}
- Firefox、Safari 和Chrome 中的XMLHttpRequest 對象與IE 中的XDomainRequest 對象類似,都提供了夠用的接口,這兩個對象共同的屬性/方法如下。
- abort():用于停止正在進行的請求。
- onerror:用于替代onreadystatechange 檢測錯誤。
- onload:用于替代onreadystatechange 檢測成功。
- responseText:用于取得響應內容。
- send():用于發(fā)送請求。
5、其它跨域技術
- 利用DOM 中能夠執(zhí)行跨域請求的功能,在不依賴XHR對象的情況下也能發(fā)送某種請求。
5.1 圖像Ping
- 第一種跨域請求技術是使用
<img>標簽。 - 動態(tài)創(chuàng)建圖像經(jīng)常用于圖像Ping。圖像Ping是與服務器進行簡單、單向的跨域通信的一種方式。請求的數(shù)據(jù)是通過查詢字符串形式發(fā)送的,而響應可以是任意內容,但通常是像素圖或204 響應。
- 通過圖像Ping,瀏覽器得不到任何具體的數(shù)據(jù),但通過偵聽load和error事件,它能知道響應是什么時候接收到的。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
- 圖像Ping 最常用于跟蹤用戶點擊頁面或動態(tài)廣告曝光次數(shù)
- 圖像Ping 有兩個主要的缺點,一是只能發(fā)送GET請求,二是無法訪問服務器的響應文本。因此,圖像Ping 只能用于瀏覽器與服務器間的單向通信。
5.2 JSONP
- JSONP 是JSON with padding(填充式JSON 或參數(shù)式JSON)的簡寫,是應用JSON 的一種新方法。
- JSONP 看起來與JSON 差不多,只不過是被包含在函數(shù)調用中的JSON。
callback({ "name": "Nicholas" });
- JSONP由兩部分組成:回調函數(shù)和數(shù)據(jù)?;卣{函數(shù)是當響應到來時應該在頁面中調用的函數(shù)。回調函數(shù)的名字一般是在請求中指定的。而數(shù)據(jù)就是傳入回調函數(shù)中的JSON數(shù)據(jù)。
http://freegeoip.net/json/?callback=handleResponse
- JSONP 是通過動態(tài)<script>元素來使用的,使用時可以為src 屬性指定一個跨域URL.
//通過查詢地理定位服務來顯示你的IP 地址和位置信息。
function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " +
response.city + ", " + response.region_name);
}
var script = document.createElement("script");
//典型的JSONP請求,請求一個JSONP 地理定位服務
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
-
JSONP優(yōu)點:
- 簡單易用
- 能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信
-
JSONP不足:
- JSONP 是從其他域中加載代碼執(zhí)行。如果其他域不安全,很可能會在響應中夾帶一些惡意代碼,而此時除了完全放棄JSONP 調用之外,沒有辦法追究。
- 要確定JSONP 請求是否失敗并不容易,開發(fā)人員不得不使用計時器檢測指定時間內是否接收到了響應。
5.3 Comet
- Ajax 是一種從頁面向服務器請求數(shù)據(jù)的技術,而Comet則是一種服務器向頁面推送數(shù)據(jù)的技術。
- 有兩種實現(xiàn)Comet 的方式:長輪詢和流。
(1)長輪詢
長輪詢是傳統(tǒng)輪詢(也稱為短輪詢)的一個翻版,即瀏覽器定時向服務器發(fā)送請求,看有沒有更新的數(shù)據(jù)。
-
無論是短輪詢還是長輪詢,瀏覽器都要在接收數(shù)據(jù)之前,先發(fā)起對服務器的連接。
短輪詢的時間線 -
長輪詢把短輪詢顛倒了一下。頁面發(fā)起一個到服務器的請求,然后服務器一直保持連接打開,直到有數(shù)據(jù)可發(fā)送。發(fā)送完數(shù)據(jù)之后,瀏覽器關閉連接,隨即又發(fā)起一個到服務器的新請求。這一過程在頁面打開期間一直持續(xù)不斷。
長輪詢的時間線 兩者最大的區(qū)別在于服務器如何發(fā)送數(shù)據(jù)。短輪詢是服務器立即發(fā)送響應,無論數(shù)據(jù)是否有效,而長輪詢是等待發(fā)送響應。輪詢的優(yōu)勢是所有瀏覽器都支持,因為使用XHR 對象和setTimeout()就能實現(xiàn)。而你要做的就是決定什么時候發(fā)送請求。
(2)HTTP 流
第二種流行的Comet 實現(xiàn)是HTTP流。流不同于上述兩種輪詢,因為它在頁面的整個生命周期內只使用一個HTTP連接。具體來說,就是瀏覽器向服務器發(fā)送一個請求,而服務器保持連接打開,然后周期性地向瀏覽器發(fā)送數(shù)據(jù)
在Firefox、Safari、Opera 和Chrome 中,通過偵聽readystatechange 事件及檢測readyState的值是否為3,就可以利用XHR 對象實現(xiàn)HTTP 流。
function createStreamingClient(url, progress, finished){
var xhr = new XMLHttpRequest(),
received = 0;
xhr.open("get", url, true);
xhr.onreadystatechange = function(){
var result;
if (xhr.readyState == 3){
//只取得最新數(shù)據(jù)并調整計數(shù)器
result = xhr.responseText.substring(received);
received += result.length;
//調用progress 回調函數(shù)
progress(result);
} else if (xhr.readyState == 4){
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("streaming.php", function(data){
alert("Received: " + data);
}, function(data){
alert("Done!");
});
5.4 服務器發(fā)送事件
SSE(Server-Sent Events,服務器發(fā)送事件)是圍繞只讀Comet 交互推出的API 或者模式。SSE API用于創(chuàng)建到服務器的單向連接,服務器通過這個連接可以發(fā)送任意數(shù)量的數(shù)據(jù)。
服務器響應的MIME類型必須是text/event-stream,而且是瀏覽器中的JavaScript API 能解析格式輸出。SSE支持短輪詢、長輪詢和HTTP流,而且能在斷開連接時自動確定何時重新連接。
支持SSE 的瀏覽器有Firefox 6+、Safari 5+、Opera11+、Chrome和iOS4+版Safari。
(1)SSE API
- SSE 的JavaScript API與其他傳遞消息的JavaScriptAPI很相似。要預訂新的事件流,首先要創(chuàng)建一個新的EventSource 對象,并傳進一個入口點:
var source = new EventSource("myevents.php");
- 傳入的URL 必須與創(chuàng)建對象的頁面同源(相同的URL模式、域及端口)。
- EventSource 的實例有一個readyState屬性,值為0表示正連接到服務器,值為1表示打開了連接,值為2 表示關閉了連接。
- 另外,還有以下三個事件。
- open:在建立連接時觸發(fā)。
- message:在從服務器接收到新事件時觸發(fā)。
- error:在無法建立連接時觸發(fā)。
source.onmessage = function(event){
//服務器發(fā)回的數(shù)據(jù)以字符串形式保存在event.data 中。
var data = event.data;
//處理數(shù)據(jù)
/**如果想強制立即斷開連接并且不再重新連接,可以調用close()
方法。**/
source.close();
};
(2)事件流
- 所謂的服務器事件會通過一個持久的HTTP響應發(fā)送,這個響應的MIME類型為text/event-stream。響應的格式是純文本,最簡單的情況是每個數(shù)據(jù)項都帶有前綴data:
data: foo
data: bar
data: foo
data: bar
- 通過id:前綴可以給特定的事件指定一個關聯(lián)的ID,這個ID行位于data:行前面或后面皆可。
data: foo
id: 1
- 設置了ID 后,EventSource對象會跟蹤上一次觸發(fā)的事件。如果連接斷開,會向服務器發(fā)送一個包含名為Last-Event-ID的特殊HTTP頭部的請求,以便服務器知道下一次該觸發(fā)哪個事件。在多次連接的事件流中,這種機制可以確保瀏覽器以正確的順序收到連接的數(shù)據(jù)段。
5.5 Web Sockets
由于Web Sockets 使用了自定義的協(xié)議,所以URL模式也略有不同。。未加密的連接不再是http://,而是ws://;加密的連接也不是https://,而是wss://。
使用自定義協(xié)議而非HTTP協(xié)議的好處能夠在客戶端和服務器之間發(fā)送非常少量的數(shù)據(jù),而不必擔心HTTP 那樣字節(jié)級的開銷。由于傳遞的數(shù)據(jù)包很小,因此Web Sockets 非常適合移動應用。
使用自定義協(xié)議的缺點在于,制定協(xié)議的時間比制定JavaScript API 的時間還要長.
(1) Web Sokets API
- 要創(chuàng)建Web Socket,先實例一個WebSocket 對象并傳入要連接的URL:
var socket = new WebSocket("ws://www.example.com/server.php");
- 與XHR 類似,WebSocket 也有一個表示當前狀態(tài)的readyState 屬性。
- WebSocket.OPENING (0):正在建立連接。
- WebSocket.OPEN (1):已經(jīng)建立連接。
- WebSocket.CLOSING (2):正在關閉連接。
- WebSocket.CLOSE (3):已經(jīng)關閉連接。
- 要關閉Web Socket 連接,可以在任何時候調用close()方法。
socket.close();
/**調用了close()之后,readyState的值立即變?yōu)?(正在關閉),而在關閉連接后就會變成3。**/
(2)發(fā)送和接收數(shù)據(jù)
- Web Socket 打開之后,就可以通過連接發(fā)送和接收數(shù)據(jù)。要向服務器發(fā)送數(shù)據(jù),使用send()方法并傳入任意字符串。
var socket = new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
- 因為Web Sockets 只能通過連接發(fā)送純文本數(shù)據(jù),所以對于復雜的數(shù)據(jù)結構,在通過連接發(fā)送之前,必須進行序列化。
var message = {
time: new Date(),
text: "Hello world!",
clientId: "asdfp8734rew"
};
socket.send(JSON.stringify(message));
- 當服務器向客戶端發(fā)來消息時,WebSocket 對象就會觸發(fā)message事件。這個message 事件與其他傳遞消息的協(xié)議類似,也是把返回的數(shù)據(jù)保存在event.data 屬性中。
socket.onmessage = function(event){
var data = event.data;
//處理數(shù)據(jù)
};
(3)其它事件
- WebSocket 對象還有其他三個事件,在連接生命周期的不同階段觸發(fā)。
- open:在成功建立連接時觸發(fā)。
- error:在發(fā)生錯誤時觸發(fā),連接不能持續(xù)。
- close:在連接關閉時觸發(fā)。
- WebSocket 對象不支持DOM2級事件偵聽器,因此必須使用DOM0級語法分別定義每個事件處理程序。
var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
alert("Connection established.");
};
socket.onerror = function(){
alert("Connection error.");
};
socket.onclose = function(){
alert("Connection closed.");
};
6、安全
對于未被授權系統(tǒng)有權訪問某個資源的情況,我們稱之為CSRF(Cross-Site Request Forgery,跨站點請求偽造)。未被授權系統(tǒng)會偽裝自己,讓處理請求的服務器認為它是合法的。
受到CSRF 攻擊的Ajax程序有大有小,攻擊行為既有旨在揭示系統(tǒng)漏洞的惡作劇,也有惡意的數(shù)據(jù)竊取或數(shù)據(jù)銷毀。
-
為確保通過XHR 訪問的URL安全,通行的做法就是驗證發(fā)送請求者是否有權限訪問相應的資源。
- 要求以SSL 連接來訪問可以通過XHR 請求的資源。
- 要求每一次請求都要附帶經(jīng)過相應算法計算得到的驗證碼。
-
下列措施對防范CSRF 攻擊不起作用。
- 要求發(fā)送POST 而不是GET 請求——很容易改變。
- 檢查來源URL 以確定是否可信——來源記錄很容易偽造。
- 基于cookie 信息進行驗證——同樣很容易偽造。
小結
- Ajax 是無需刷新頁面就能夠從服務器取得數(shù)據(jù)的一種方法。關于Ajax,可以從以下幾方面來總結一下。
(1)負責Ajax 運作的核心對象是XMLHttpRequest(XHR)對象。
(2)XHR 對象由微軟最早在IE5 中引入,用于通過JavaScript 從服務器取得XML 數(shù)據(jù)。
(3)在此之后,F(xiàn)irefox、Safari、Chrome 和Opera 都實現(xiàn)了相同的特性,使XHR 成為了Web 的一個事實標準。
(4)雖然實現(xiàn)之間存在差異,但XHR對象的基本用法在不同瀏覽器間還是相對規(guī)范的,因此可以放心地用在Web 開發(fā)當中。
- 同源策略是對XHR 的一個主要約束,它為通信設置了“相同的域、相同的端口、相同的協(xié)議”這一限制。試圖訪問上述限制之外的資源,都會引發(fā)安全錯誤,除非采用被認可的跨域解決方案。
- 這個解決方案叫做CORS(Cross-Origin Resource Sharing,跨源資源共享),IE8 通過XDomainRequest 對象支持CORS,其他瀏覽器通過XHR對象原生支持CORS。圖像Ping 和JSONP 是另外兩種跨域通信的技術,但不如CORS 穩(wěn)妥。
Comet 是對Ajax 的進一步擴展,讓服務器幾乎能夠實時地向客戶端推送數(shù)據(jù)。實現(xiàn)Comet 的手段主要有兩個:長輪詢和HTTP流。所有瀏覽器都支持長輪詢,而只有部分瀏覽器原生支持HTTP 流。SSE(Server-SentEvents,服務器發(fā)送事件)是一種實現(xiàn)Comet 交互的瀏覽器API,既支持長輪詢,也支持HTTP 流。
Web Sockets 是一種與服務器進行全雙工、雙向通信的信道。與其他方案不同,Web Sockets 不使用HTTP 協(xié)議,而使用一種自定義的協(xié)議。這種協(xié)議專門為快速傳輸小數(shù)據(jù)設計。雖然要求使用不同的Web 服務器,但卻具有速度上的優(yōu)勢。
