AJAX(XMLHttpRequest)進行跨域請求方法詳解(一)
注意:以下程式碼請在Firefox 3.5、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 容器內。IE8、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 是如何工作的。
1,簡單請求
什麼樣的請求算是簡單請求呢?簡單請求必須滿足下面2點:
a,只使用 GET、POST 進行的請求,這裡的POST只包括髮送給伺服器的資料型別(Content-Type)必須是 application/x-www-form-urlencoded、multipart/form-data 或者 text/plain中一個。
b,HTTP 請求沒有設定自定義的請求頭,如我們常用的 X-JSON。
先使用下面的程式碼進行測試:
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <htmlxmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>孟憲會之AJAX跨域請求測試</title>
- </head>
- <body>
- <inputtype='button'value='開始測試'onclick='crossDomainRequest()'/>
- <divid="content"></div>
- <mce:scripttype="text/javascript"><!--
- var xhr =
- 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:scriptrunat="server"><!--
- protected void Page_Load(object sender, EventArgs e)
- {
- Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");
- Response.Write("孟憲會向各位朋友發來賀電:你的第一個跨域測試成功啦!!!");
- }
- // --></mce:script>
點選 “開始測試” 按鈕,傳送的請求和返回的響應資訊如下:
- 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 源下的指令碼才可以進行訪問。
好了,上面我們就完成了一個簡單的跨域請求,怎麼樣?感覺還是不錯的吧。下面我們進行一個“預檢”請求。