高程3總結#第21章Ajax與Comet

Ajax與Comet

XMLHttpRequest對象

  • IE5是第一款引入XHR對象的瀏覽器,在IE5中,XHR對象是經過MSXML庫中的一個ActiveX對象實現的javascript

    //適用於 IE7 以前的版本
    function createXHR(){
    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);
    }
  • 這個函數會盡力根據IE中可用的MSXML庫的狀況建立最新版本的XHR對象
  • IE7+、Firefox、Opera、Chrome、Safari都支持原生的XHR對象,這些瀏覽器中建立XHR對象,可使用XMLHttpRequest構造函數php

    var xhr=new XMLHttpRequest();
  • 若是還必需要支持IE的更早版本,能夠在createHXR()函數中加入對原生XHR對象的支持java

    function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
      return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined"){
      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.");
    }
    }
  • 這個函數中新增的代碼首先檢測原生XHR對象是否存在,若是存在則返回它的新實例,若是原生對象不存在,則檢測ActiveX對象,若是這兩種對象都不存在,就拋出一個錯誤,而後就可使用下面的代碼在全部瀏覽器中建立XHR對象了算法

    var xhr=createXHR();

XHR的用法

  • 在使用XHR對象時,要調用的第一個方法是open(),接收3個參數:要發送的請求的類型(get或者post)、請求的URL和表示是否異步發送請求的布爾值json

    xhr.open("get","example.php",false);
  • 這行代碼會啓動一個針對example.php的GET請求
  • URL相對於執行代碼的當前頁面
  • open()方法並不會真正發送請求,而是啓動一個請求以備發送
  • 要想發送特定的請求,調用send()方法跨域

    xhr.open("get","example.txt",false);
    xhr.send(null);
  • send()方法接收一個參數,要做爲請求主體發送的數據,若是不須要經過請求主體發送數據,必須傳入null
  • 響應的數據會自動填充XHR對象的屬性瀏覽器

    • responseText,做爲響應主體被返回的文本
    • responseXML,若是響應的內容類型是"text/xml"或"application/xml",這個屬性中將保存包含着響應數據的XML DOM文檔
    • status,響應的HTTP狀態
    • statusText,HTTP狀態的說明
  • XHR對象的readyState屬性表示請求響應過程的當前活動階段緩存

    • 0,未初始化,還沒有調用open()方法
    • 1,啓動,已經調用open()方法,但還沒有調用send()方法
    • 2,發送,已經調用send()方法,但還沒有接收到響應
    • 3,接收,已經接收到部分響應數據
    • 4,完成,已經接收到所有響應數據,並且已經能夠在客戶端使用了
  • 必須在調用open()以前指定onreadystatechange事件處理程序才能確保跨瀏覽器兼容性安全

    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()方法來取消異步請求

HTTP頭部信息

  • 在發送XHR請求的同時,還會發送下列頭部信息服務器

    • Accept,瀏覽器可以處理的內容類型
    • Accept-Charset,瀏覽器可以顯示的字符集
    • Accept-Encoding,瀏覽器可以處理的壓縮編碼
    • Accept-Language,瀏覽器當前設置的語言
    • Connection,瀏覽器與服務器之間鏈接的類型
    • Cookie,當前頁面設置的任何Cookie
    • Host,發出請求的頁面所在的域
    • Referer,發出請求的頁面的URI
    • User-Agent,瀏覽器的用戶代理字符串
  • setRequestHeader()方法,能夠設置自定義的請求頭部信息,這個方法接收兩個參數:頭部字段名稱和頭部字段的值。要成功發送頭部信息,必須在調用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);

GET請求

  • GET是最多見的請求類型,最經常使用於向服務器查詢某些信息,必要時能夠將查詢字符串參數追加到URI的末尾,以便將信息發送給服務器
  • 使用GET請求常常會發生一個錯誤,就是查詢字符串的格式問題。
  • 查詢字符串中每一個參數的名稱和值都必須使用encodeURIComponent()進行編碼,而後才能放到URL的末尾,並且全部的名-值對都必須由&分隔

    xhr.open("get","example.php?name1=value1&name2=value2",true)
  • 向現有URL的末尾添加查詢字符串參數

    function addURLParam(url, name, value) {
      url += (url.indexOf("?") == -1 ? "?" : "&");
      url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
      return url;
    }
  • addURLParam()函數接收3個參數:要添加參數的URL、參數的名稱和參數的值

POST請求

  • POST請求,一般用於向服務器發送應該被保存的數據

    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));
    }

XMLHttpRequest 2級

FormData

  • FormData爲序列化表單以及建立與表單格式相同的數據,提供了便利

    var data=new FormData();
    data.append("name","Nicholas");
  • 這個append()方法接收兩個參數,鍵和值
  • 建立了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));

超時設定

  • IE8爲XHR對象添加了一個timeout屬性,表示請求在等待響應多少毫秒以後就會終止,在給timeout設置一個數值後,若是在規定的時間內瀏覽器尚未接收到響應,那麼就會觸發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);

overrideMimeType()方法

  • Firefox最先引入了overrideMimeType()方法,用於重寫XHR響應的MIME類型
  • 經過調用overrideMimeType()方法,能夠保證把響應當作XML而非純文原本處理

    var xhr = createXHR();
    xhr.open("get", "text.php", true);
    xhr.overrideMimeType("text/xml");
    xhr.send(null);

進度事件

  • 6個進度事件

    • loadstart,在接收到響應數據的第一個字節時觸發
    • progress,在接收響應期間持續不斷地觸發
    • error,在請求發生錯誤時觸發
    • abort,在由於調用abort()方法而終止鏈接時觸發
    • load,在接收到完整的響應數據時觸發
    • loadend,在通訊完成或者觸發error、abort或load事件後觸發

load事件

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);

progress事件

var xhr = createXHR();
xhr.onload = function(event){
if ((xhr.status >= 200 && xhr.status < 300) ||
    xhr.status == 304){
  alert(xhr.responseText);
} else {
  alert("Request was unsuccessful: " + xhr.status);
}
};
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);
  • 爲確保正常執行,必須在調用open()方法以前添加onprogress事件處理程序

跨源資源共享

  • CORS,跨資源共享,定義了在必須訪問跨源資源時,瀏覽器遠服務器應該如何溝通。CORS基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功仍是應該失敗

IE對CORS的實現

  • 微軟在IE8中引入了XDR類型,這個對象與XHR類型,可是能實現安全可靠的跨域通訊

    • cookie不會隨請求發佈,也不會隨響應返回
    • 只能設置請求頭部信息中的Content-Type字段
    • 不能訪問響應頭部信息
    • 只支持GET和POST請求
  • XDR對象的使用方法與XHR對象很是類似,也是建立一個XDomainRequest的實例,調用open()方法,再調用send()方法,與XHR對象的open()方法不一樣,XDR對象的open()方法只接收2個參數:請求類型和URL
  • 全部XDR請求都是異步執行的,不能用它來建立同步請求,請求返回以後,會觸發load事件,響應的數據也會保存在responseText屬性中

    var xdr = new XDomainRequest();
    xdr.onload = function(){
      alert(xdr.responseText);
    };
    xdr.open("get", "http://www.somewhere-else.com/page/");
    xdr.send(null)
  • 請求返回前調用abort()方法能夠終止請求

    xdr.abort();//終止請求
  • 與XHR同樣,XDR對象也支持timeout屬性以及ontimeout事件處理程序

    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屬性,用來表示發送數據的格式

    var xdr = new XDomainRequest();
    xdr.onload = function(){
      alert(xdr.responseText);
    };
    xdr.onerror = function(){
      alert("An error occurred.");
    };
    xdr.open("post", "http://www.somewhere-else.com/page/");
    xdr.contentType = "application/x-www-form-urlencoded";
    xdr.send("name1=value1&name2=value2");

其餘瀏覽器對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);
  • 安全限制

    • 不能使用setRequsetHeader()設置自定義頭部
    • 不能發送和接收cookie
    • 調用getAllResponseHeaders()方法總會返回空字符串

Preflighted Requests

  • 經過Preflighted Request的透明服務器驗證機制支持開發人員使用自定義的頭部、GET和POST以外的方法,以及不一樣類型的主體內容
  • 這種請求使用OPITONS方法,發送下列頭部

    • Origin,與簡單的請求相同
    • Access-Control-Method,請求自身使用的方法
    • Access-Control-Headers,自定義的頭部信息,多個頭部以逗號分隔
    Origin: http://www.nczonline.net
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: NCZ
  • 服務器能夠決定是否容許這種類型的請求,服務器在響應中發送以下頭部與瀏覽器進行溝通

    • Access-Control-Allow-Origin,與簡單的請求相同
    • Access-Control-Allow-Methods,容許的方法,多個方法以逗號分隔
    • Access-Control-Allow-Headers,容許的頭部,多個頭部以逗號分隔
    • Access-Control-Max-Age,應該將這個Preflight請求緩存多長時間,以秒錶示
    Access-Control-Allow-Origin: http://www.nczonline.net
    Access-Control-Allow-Methods: POST, GET
    Access-Control-Allow-Headers: NCZ
    Access-Control-Max-Age: 1728000

帶憑據的請求

  • 經過將withCredentials屬性設置爲true,能夠指定某個請求應該發送憑據

    Access-Control-Allow-Credentials:true
  • 若是發送的是帶憑據的請求,但服務器的響應中沒有包含這個頭部,那麼瀏覽器就不會把響應交給JavaScript,因而responseText中將是空字符串,status的值爲0,並且會調用onerror()事件處理程序

跨瀏覽器的CORS

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(),用於發送請求

其餘跨域技術

圖像Ping

  • 一個網頁能夠從任何網頁中加載圖像,不用擔憂跨域不跨域
  • 經過圖像Ping,瀏覽器得不到任何具體的數據,但經過偵聽load和error事件,它能知道響應何時接收到的

    var img = new Image();
    img.onload = img.onerror = function(){
      alert("Done!");
    };
    img.src = "http://www.example.com/test?name=Nicholas";
  • 圖像Ping最經常使用於跟蹤用戶點擊頁面或動態廣告曝光次數,圖像Ping有兩個主要的缺點,一是隻能發送GET請求,二是沒法訪問服務器的響應文本。所以圖像Ping只能用於瀏覽器與服務器間的單向通訊

JSONP

  • JSONP由兩部分組成:回調函數和數據,回調函數是當響應到來時應該在頁面中調用的函數,回調函數的名字通常是在請求中指定的,而數據就是傳入回調函數中的JSON數據

    function handleResponse(response){
      alert("You’re at IP address " + response.ip + ", which is in " +
            response.city + ", " + response.region_name);
    }
    var script = document.createElement("script");
    script.src = "http://freegeoip.net/json/?callback=handleResponse";
    document.body.insertBefore(script, document.body.firstChild);
  • 優勢在於可以直接訪問響應文本,支持在瀏覽器與服務器之間雙向通訊

Comet

  • 兩種實現Comet的方式

    • 長輪詢

      • 短輪詢時間線
        圖片描述
      • 長輪詢把短輪詢顛倒了一下。頁面發起一個到服務器的請求,而後服務器一直保持鏈接打開,直到有數據可發送。發送完數據以後,瀏覽器關閉鏈接,隨即又發起一個到服務器的新請求,這一過程在頁面打開期間一直持續不斷
        圖片描述
      • 在頁面的整個生命週期內只使用一個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){
              //只取得最新數據並調整計數器
              result = xhr.responseText.substring(received);
              received += result.length;
              //調用 progress 回調函數
              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!");
        });
      • 這個createStreamingClient()函數接收三個參數:要鏈接的URL、在接收到數據時調用的函數以及關閉鏈接時調用的函數

服務器發送事件

  • SSE是圍繞只讀Comet交互推出的API或者模式

    var source=new EventSource("myevents.php");
  • EventSource的實例有一個readyState屬性,值爲0表示正鏈接到服務器,值爲1表示打開了鏈接,值爲2表示關閉了鏈接
  • 另外還有三個事件

    • open,在創建鏈接時觸發
    • message,在從服務器接收到新事件時觸發
    • error,在沒法創建鏈接時觸發

Web Sockets

  • Web Sockets的目標是在一個單獨的持久鏈接上提供全雙工、雙向通訊
  • 要建立Web Socket,先實例一個WebSocket對象並傳入要鏈接的URL

    var socket = new WebSocket("ws://www.example.com/server.php");
  • 實例化WebSocket對象後,瀏覽器會立刻嘗試建立鏈接,與XHR相似,WebSocket也有一個表示當前狀態的readyState屬性,這個屬性的值與XHR並不相同

    • WebSocket.OPENING(0),正在創建鏈接
    • WebSocket.OPEN(1),已經創建鏈接
    • WebSocket.CLOSING(2),正在關閉鏈接
    • WebSocket.CLOSE(3),已經關閉鏈接
  • WebSocket沒有readystatechange事件,不過有其餘事件,對應着不一樣的狀態,readyState的值永遠從0開始
  • 要關閉Web Socket鏈接,能夠在任什麼時候候調用close()方法

    socket.close();
  • 調用close()以後,readyState的值當即變爲2,而關閉鏈接後就會變成3
  • 使用send()方法並傳入任意字符串

    var socket = new WebSocket("ws://www.example.com/server.php");
    socket.send("Hello world!");
    //將數據序列化爲JSON字符串,而後發送到服務器
    var message = {
      time: new Date(),
      text: "Hello world!",
      clientId: "asdfp8734rew"
    };
    socket.send(JSON.stringify(message));
    //當服務器收到消息時,WebSocket對象就會觸發message事件,這個message事件與其餘傳遞消息的協議相似,也是把返回的數據保存在event.data屬性中
    socket.onmessage = function(event){
      var data = event.data;
      //處理數據
    };
  • 其餘事件

    • open,在成功創建鏈接時觸發
    • error,在發生錯誤時觸發,鏈接不能持續
    • close,在鏈接關閉時觸發
    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.");
    };

SSE與Web Sockets

  • 考慮是使用 SSE 仍是使用 Web Sockets 時,能夠考慮以下幾個因素。

    • 首先,你是否有自由度創建和維護 Web Sockets服務器?由於 Web Socket 協議不一樣於 HTTP,因此現有服務器不能用於 Web Socket 通訊。SSE 卻是經過常規 HTTP 通訊,所以現有服務器就能夠知足需求。
    • 第二個要考慮的問題是到底需不須要雙向通訊。若是用例只需讀取服務器數據(如比賽成績),那麼 SSE 比較容易實現。若是用例必須雙向通訊(如聊天室),那麼 Web Sockets 顯然更好。在不能選擇 Web Sockets 的狀況下,組合 XHR 和 SSE 也是能實現雙向通訊的。

安全

  • 爲確保經過 XHR 訪問的 URL 安全,通行的作法就是驗證發送請求者是否有權限訪問相應的資源

    • 要求以 SSL 鏈接來訪問能夠經過 XHR 請求的資源。
    • 要求每一次請求都要附帶通過相應算法計算獲得的驗證碼。請注意,下列措施對防範 CSRF 攻擊不起做用。
    • 要求發送 POST 而不是 GET 請求——很容易改變。
    • 檢查來源 URL 以肯定是否可信——來源記錄很容易僞造。
    • 基於 cookie 信息進行驗證——一樣很容易僞造
相關文章
相關標籤/搜索