AJAX(XMLHttpRequest)進行跨域請求方法詳解

注意:如下代碼請在Firefox 3.五、Chrome 3.0、Safari 4以後的版本中進行測試。IE8的實現方法與其餘瀏覽不一樣。

跨域請求,顧名思義,就是一個站點中的資源去訪問另一個不一樣域名站點上的資源。這種狀況很常見,好比說經過 style 標籤加載外部樣式表文件、經過 img 標籤加載外部圖片、經過 script 標籤加載外部腳本文件、經過 Webfont 加載字體文件等等。默認狀況下,腳本訪問文檔屬性等數據採用的是同源策略(Same origin policy)。

那麼,什麼是同源策略呢?若是兩個頁面的協議、域名和端口是徹底相同的,那麼它們就是同源的。同源策略是爲了防止從一個地址加載的文檔或腳本訪問或者設置 從另一個地址加載的文檔的屬性。若是兩個頁面的主域名相同,則還能夠經過設置 document.domain 屬性將它們認爲是同源的。

隨着 Web2.0 和 SNS 的興起,Web 應用對跨域訪問的需求也愈來愈多,可是,在腳本中進行跨域請求是受安全性限制的,Web 開發人員迫切須要提供一種更安全、方便的跨域請求方式來融合(Mashup)本身的 Web 應用。這樣作的一個好處就是能夠將請求分攤到不一樣的服務器,減輕單個服務器壓力以提升響應速度;另一個好處是能夠將不一樣的業務邏輯分佈到不一樣的服務器上 以下降負載。

值得慶幸的是,跨域請求的標準已經出臺,主流瀏覽器也已經實現了這一標準。W3C 工做組中的 Web Applications Working Group(Web 應用工做組)發佈了一個 Cross-Origin Resource Sharing(跨域資源共享,該規範地址:http://www.w3.org/TR/access-control/和http: //dev.w3.org/2006/waf/access-control/) 推薦規範來解決跨域請求的問題。該規範提供了一種更安全的跨域數據交換方法。具體規範的介紹能夠訪問上面提供的網站地址。值得注意的是:該規範只能應用在 相似 XMLHttprequest 這樣的 API 容器內。IE八、Firefox 3.5 及其之後的版本、Chrome瀏覽器、Safari 4 等已經實現了 Cross-Origin Resource Sharing 規範,已經能夠進行跨域請求了。

Cross-Origin Resource Sharing 的工做方式是經過添加 HTTP 頭的方法來判斷哪些資源容許 Web 瀏覽器訪問該域名下的信息。然而,對於那些 HTTP 請求致使用戶數據產生反作用的請求方法(特別是對於除了GET、某些 MIME 類型的 POST 以外的 HTTP方法),該規範要求瀏覽器對請求進行「預先驗」,經過發送 HTTP 的 OPTIONS 請求頭詢問服務器有哪些支持的方法,在徵得服務器的贊成後,再使用實際的 HTTP 請求方法發送實際的請求。服務器也能夠通知客戶端是否須要將驗證信息(如 Cookie 和 HTTP Authentication 數據)隨同請求一塊兒發送。

下面咱們就採用實際的例子說明 Cross-Origin Resource Sharing 是如何工做的。javascript

 

1,簡單請求html

 

什麼樣的請求算是簡單請求呢?簡單請求必須知足下面2點:
a,只使用 GET、POST 進行的請求,這裏的POST只包括髮送給服務器的數據類型(Content-Type)必須是 application/x-www-form-urlencoded、multipart/form-data 或者 text/plain中一個。
b,HTTP 請求沒有設置自定義的請求頭,如咱們經常使用的 X-JSON。java

 

先使用下面的代碼進行測試:跨域

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
  <title>孟憲會之AJAX跨域請求測試</title>  
</head>  
<body>  
  <input type='button' value='開始測試' onclick='crossDomainRequest()' />  
  <div id="content"></div>  
  <mce:script type="text/javascript"><!--  
    var xhr = new XMLHttpRequest();  
    var url = 'http://dotnet.aspx.cc/SimpleCrossSiteRequests.aspx';  
    function crossDomainRequest() {  
      document.getElementById("content").innerHTML = "開始……";  
      if (xhr) {  
        xhr.open('GET', url, true);  
        xhr.onreadystatechange = handler;  
        xhr.send();  
      } else {  
        document.getElementById("content").innerHTML = "不能建立 XMLHttpRequest";  
      }  
    }  
    function handler(evtXHR) {  
      if (xhr.readyState == 4) {  
        if (xhr.status == 200) {  
          var response = xhr.responseText;  
          document.getElementById("content").innerHTML = "結果:" + response;  
        } else {  
          document.getElementById("content").innerHTML = "不容許跨域請求。";  
        }  
      }  
      else {  
        document.getElementById("content").innerHTML += "<br/>執行狀態 readyState:" + xhr.readyState;  
      }  
    }  
// --></mce:script>  
</body>  
</html>

而後,在服務器建立 CrossDomainRequest.aspx 的內容以下:瀏覽器

<%@ Page Language="C#" %>  
<mce:script runat="server"><!--  
  protected void Page_Load(object sender, EventArgs e)  
  {  
    Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");  
    Response.Write("孟憲會向各位朋友發來賀電:你的第一個跨域測試成功啦!!!");  
  }

點擊 「開始測試」 按鈕,發送的請求和返回的響應信息以下:安全

GET /SimpleCrossSiteRequests.aspx HTTP/1.1  
Host: dotnet.aspx.cc  
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: zh-cn,zh;q=0.5  
Accept-Encoding: gzip,deflate  
Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7  
Keep-Alive: 300  
Connection: keep-alive  
Referer: http://www.meng_xian_hui.com:801/CrossDomainAjax/SimpleCrossSiteRequests.html  
Origin: http://www.meng_xian_hui.com:801  
HTTP/1.x 200 OK  
Date: Sun, 10 Jan 2010 13:52:00 GMT  
Server: Microsoft-IIS/6.0  
X-Powered-By: ASP.NET  
X-AspNet-Version: 2.0.50727  
Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801  
Set-Cookie: ASP.NET_SessionId=wk5v5nrs5wbfi4rmpjy2jujb; path=/; HttpOnly  
Cache-Control: private  
Content-Type: text/html; charset=utf-8  
Content-Length: 84

須要特別注意的是:在請求信息中,瀏覽器使用 Origin 這個 HTTP 頭來標識該請求來自於 http://www.meng_xian_hui.com:801;在返回的響應信息中,使用 Access-Control-Allow-Origin 頭來控制哪些域名的腳本能夠訪問該資源。若是設置 Access-Control-Allow-Origin:*,則容許全部域名的腳本訪問該資源。若是有多個,則只須要使用逗號分隔開便可。

注意:在服務器端,Access-Control-Allow-Origin 響應頭 http://www.meng_xian_hui.com:801 中的端口信息不能省略。

有人可能會想:本身發送請求頭會如何呢?好比 xhr.setRequestHeader("Origin","http://www.meng_xian_hui.com:801"); 實踐證實,本身設置 Origin 頭是不行的。

是否是如今就能夠採用 XMLHttpRequest 來請求任意一個網站的數據呢?仍是不行的。容許哪些域名能夠訪問,還須要服務器來設置 Access-Control-Allow-Origin 頭來進行受權,具體的代碼是:

Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");

這行代碼就告訴瀏覽器,只有來自 http://www.meng_xian_hui.com:801 源下的腳本才能夠進行訪問。

好了,上面咱們就完成了一個簡單的跨域請求,怎麼樣?感受仍是不錯的吧。下面咱們進行一個「預檢」請求。服務器


2,預檢請求app

預檢請求首先須要向另一個域名的資源發送一個 HTTP OPTIONS 請求頭,其目的就是爲了判斷實際發送的請求是不是安全的。下面的2種狀況須要進行預檢:
a,不是上面的簡單請求,好比使用Content-Type 爲 application/xml 或 text/xml 的 POST 請求
b,在請求中設置自定義頭,好比 X-JSON、X-MENGXIANHUI 等dom

 

注意:在 iis 裏進行測試,必須在「應用程序擴展」裏面配置 .aspx 擴展的動做容許 OPTIONS。

下面咱們舉一個預檢的請求:測試

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
<title>孟憲會之AJAX跨域請求測試</title>  
</head>  
<body>  
  <input type='button' value='開始測試' onclick='crossDomainRequest()' />  
  <div id="content"></div>  
  <mce:script type="text/javascript"><!--  
  var xhr = new XMLHttpRequest();  
  var url = 'http://dotnet.aspx.cc/PreflightedRequests.aspx';  
  function crossDomainRequest() {  
    document.getElementById("content").innerHTML = "開始進行請求……";  
    if (xhr) {  
      var xml = "<root>測試</root>";  
      xhr.open('POST', url, true);  
      xhr.setRequestHeader("POWERED-BY-MENGXIANHUI", "Approve");  
      xhr.setRequestHeader("Content-Type", "application/xml");  
      xhr.onreadystatechange = handler;  
      xhr.send(xml);  
    } else {  
    document.getElementById("content").innerHTML = "不能建立 XMLHttpRequest。";  
    }  
  }  
  function handler(evtXHR) {  
    if (xhr.readyState == 4) {  
      if (xhr.status == 200) {  
        var response = xhr.responseText;  
        document.getElementById("content").innerHTML = "結果:" + response;  
      } else {  
        document.getElementById("content").innerHTML = "不能進行跨越訪問。";  
      }  
    }  
    else {  
      document.getElementById("content").innerHTML += "<br/>執行狀態 readyState:" + xhr.readyState;  
    }  
  }  
// --></mce:script>  
</body>  
</html>

點擊「開始測試」按鈕,將會執行下面的一系列請求。

OPTIONS /PreflightedRequests.aspx HTTP/1.1  
Host: dotnet.aspx.cc  
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: zh-cn,zh;q=0.5  
Accept-Encoding: gzip,deflate  
Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7  
Keep-Alive: 300  
Connection: keep-alive  
Origin: http://www.meng_xian_hui.com:801  
Access-Control-Request-Method: POST  
Access-Control-Request-Headers: powered-by-mengxianhui  
HTTP/1.x 200 OK  
Date: Sun, 10 Jan 2010 14:00:34 GMT  
Server: Microsoft-IIS/6.0  
X-Powered-By: ASP.NET  
X-AspNet-Version: 2.0.50727  
Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801  
Access-Control-Allow-Methods: POST, GET, OPTIONS  
Access-Control-Allow-Headers: POWERED-BY-MENGXIANHUI  
Access-Control-Max-Age: 30  
Set-Cookie: ASP.NET_SessionId=5npqri55dl1k1zvij1tlw3re; path=/; HttpOnly  
Cache-Control: private  
Content-Length: 0  
POST /PreflightedRequests.aspx HTTP/1.1  
Host: dotnet.aspx.cc  
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: zh-cn,zh;q=0.5  
Accept-Encoding: gzip,deflate  
Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7  
Keep-Alive: 300  
Connection: keep-alive  
POWERED-BY-MENGXIANHUI: Approve  
Content-Type: application/xml; charset=UTF-8  
Referer: http://www.meng_xian_hui.com:801/CrossDomainAjax/PreflightedRequests.html  
Content-Length: 19  
Origin: http://www.meng_xian_hui.com:801  
Pragma: no-cache  
Cache-Control: no-cache  
<root>測試</root>  
HTTP/1.x 200 OK  
Date: Sun, 10 Jan 2010 14:00:34 GMT  
Server: Microsoft-IIS/6.0  
X-Powered-By: ASP.NET  
X-AspNet-Version: 2.0.50727  
Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801  
Set-Cookie: ASP.NET_SessionId=byvose45zmtbqy45d2a1jf2i; path=/; HttpOnly  
Cache-Control: private  
Content-Type: text/html; charset=utf-8  
Content-Length: 65

以上的代碼反映了預檢請求的執行過程:首先發送 OPTIONS 請求頭,用來向服務器諮詢服務器的更多信息,以便爲後續的真實請求作準備。好比是否支持 POST 方法等。值得注意的是:

瀏覽器還發送 Access-Control-Request-Method: POST 和 Access-Control-Request-Headers: powered-by-mengxianhui 請求頭。

注意:以上過程是第一次請求的時候的過程,若是在 30 秒內重複點擊按鈕,你能夠看不到 OPTIONS 這一過程。則執行過程是這樣的:

POST /PreflightedRequests.aspx HTTP/1.1  
Host: dotnet.aspx.cc  
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.5.30729)  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: zh-cn,zh;q=0.5  
Accept-Encoding: gzip,deflate  
Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7  
Keep-Alive: 300  
Connection: keep-alive  
POWERED-BY-MENGXIANHUI: Approve  
Content-Type: application/xml; charset=UTF-8  
Referer: http://www.meng_xian_hui.com:801/CrossDomainAjax/PreflightedRequests.html  
Content-Length: 19  
Origin: http://www.meng_xian_hui.com:801  
Pragma: no-cache  
Cache-Control: no-cache  
<root>測試</root>  
HTTP/1.x 200 OK  
Date: Sun, 10 Jan 2010 14:06:32 GMT  
Server: Microsoft-IIS/6.0  
X-Powered-By: ASP.NET  
X-AspNet-Version: 2.0.50727  
Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801  
Set-Cookie: ASP.NET_SessionId=qs1c4urxywdbdx55u04pvual; path=/; HttpOnly  
Cache-Control: private  
Content-Type: text/html; charset=utf-8  
Content-Length: 65

爲何會這樣?細心的童鞋可能注意到了,在服務器端有一行代碼 Response.AddHeader("Access-Control-Max-Age", "30");  它是用來設置預檢的有效時間的,單位是秒。這一點要特別注意。

相關文章
相關標籤/搜索