注意:以下代碼請在Firefox 3.5、Chrome 3.0、Safari 4之後的版本中進行測試。IE8的實現方法與其他瀏覽不同。
2,預檢請求
預檢請求首先需要向另外一個網域名稱的資源發送一個 HTTP OPTIONS 要求標頭,其目的就是為了判斷實際發送的請求是否是安全的。下面的2種情況需要進行預檢:
a,不是上面的簡單請求,比如使用Content-Type 為 application/xml 或 text/xml 的 POST 請求
b,在請求中設定自訂頭,比如 X-JSON、X-MENGXIANHUI 等
注意:在 iis 裡進行測試,必須在“應用程式擴充”裡面配置 .aspx 擴充的動作允許 OPTIONS。
下面我們舉一個預檢的請求:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"<br /> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><br /><html xmlns="http://www.w3.org/1999/xhtml"><br /><head><br /><title>孟憲會之AJAX跨域請求測試</title><br /></head><br /><body><br /> <input type='button' value='開始測試' onclick='crossDomainRequest()' /><br /> <div id="content"></div><br /> <mce:script type="text/javascript"><!--<br /> var xhr = new XMLHttpRequest();<br /> var url = 'http://dotnet.aspx.cc/PreflightedRequests.aspx';<br /> function crossDomainRequest() {<br /> document.getElementById("content").innerHTML = "開始進行請求……";<br /> if (xhr) {<br /> var xml = "<root>測試</root>";<br /> xhr.open('POST', url, true);<br /> xhr.setRequestHeader("POWERED-BY-MENGXIANHUI", "Approve");<br /> xhr.setRequestHeader("Content-Type", "application/xml");<br /> xhr.onreadystatechange = handler;<br /> xhr.send(xml);<br /> } else {<br /> document.getElementById("content").innerHTML = "不能建立 XMLHttpRequest。";<br /> }<br /> }<br /> function handler(evtXHR) {<br /> if (xhr.readyState == 4) {<br /> if (xhr.status == 200) {<br /> var response = xhr.responseText;<br /> document.getElementById("content").innerHTML = "結果:" + response;<br /> } else {<br /> document.getElementById("content").innerHTML = "不能進行跨越訪問。";<br /> }<br /> }<br /> else {<br /> document.getElementById("content").innerHTML += "<br/>執行狀態 readyState:" + xhr.readyState;<br /> }<br /> }<br />// --></mce:script><br /></body><br /></html>
上面的例子我們發送 xml 格式的資料,並且,發送一個非標準的HTTP頭 POWERED-BY-MENGXIANHUI 來說明伺服器端該如何設定回應標頭的。
在伺服器端,PreflightedRequests.aspx 的內容如下:
<%@ Page Language="C#" %><br /><mce:script runat="server"><!--<br /> protected void Page_Load(object sender, EventArgs e)<br /> {<br /> if (Request.HttpMethod.Equals("GET"))<br /> {<br /> Response.Write("這個頁面是用來測試跨域 POST 請求的,直接瀏覽意義不大。");<br /> }<br /> else if (Request.HttpMethod.Equals("OPTIONS"))<br /> {<br /> //通知用戶端允許預檢請求。並設定緩衝時間<br /> Response.ClearContent();<br /> Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");<br /> Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");<br /> Response.AddHeader("Access-Control-Allow-Headers", "POWERED-BY-MENGXIANHUI");<br /> Response.AddHeader("Access-Control-Max-Age", "30");<br /> //此過程無需返回資料<br /> Response.End();<br /> }<br /> else if (Request.HttpMethod.Equals("POST"))<br /> {<br /> if (Request.Headers["Origin"].Equals("http://www.meng_xian_hui.com:801"))<br /> {<br /> System.Xml.XmlDocument doc = new System.Xml.XmlDocument();<br /> doc.Load(Request.InputStream);<br /> Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");<br /> Response.Write("您提交的資料是:<br/><br/>" + Server.HtmlEncode(doc.OuterXml));<br /> }<br /> else<br /> {<br /> Response.Write("不允許你的網站請求。");<br /> }<br /> }<br /> }<br />// --></mce:script>
點擊“開始測試”按鈕,將會執行下面的一系列請求。
OPTIONS /PreflightedRequests.aspx HTTP/1.1<br />Host: dotnet.aspx.cc<br />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)<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />Accept-Language: zh-cn,zh;q=0.5<br />Accept-Encoding: gzip,deflate<br />Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7<br />Keep-Alive: 300<br />Connection: keep-alive<br />Origin: http://www.meng_xian_hui.com:801<br />Access-Control-Request-Method: POST<br />Access-Control-Request-Headers: powered-by-mengxianhui<br />HTTP/1.x 200 OK<br />Date: Sun, 10 Jan 2010 14:00:34 GMT<br />Server: Microsoft-IIS/6.0<br />X-Powered-By: ASP.NET<br />X-AspNet-Version: 2.0.50727<br />Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801<br />Access-Control-Allow-Methods: POST, GET, OPTIONS<br />Access-Control-Allow-Headers: POWERED-BY-MENGXIANHUI<br />Access-Control-Max-Age: 30<br />Set-Cookie: ASP.NET_SessionId=5npqri55dl1k1zvij1tlw3re; path=/; HttpOnly<br />Cache-Control: private<br />Content-Length: 0<br />POST /PreflightedRequests.aspx HTTP/1.1<br />Host: dotnet.aspx.cc<br />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)<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />Accept-Language: zh-cn,zh;q=0.5<br />Accept-Encoding: gzip,deflate<br />Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7<br />Keep-Alive: 300<br />Connection: keep-alive<br />POWERED-BY-MENGXIANHUI: Approve<br />Content-Type: application/xml; charset=UTF-8<br />Referer: http://www.meng_xian_hui.com:801/CrossDomainAjax/PreflightedRequests.html<br />Content-Length: 19<br />Origin: http://www.meng_xian_hui.com:801<br />Pragma: no-cache<br />Cache-Control: no-cache<br /><root>測試</root><br />HTTP/1.x 200 OK<br />Date: Sun, 10 Jan 2010 14:00:34 GMT<br />Server: Microsoft-IIS/6.0<br />X-Powered-By: ASP.NET<br />X-AspNet-Version: 2.0.50727<br />Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801<br />Set-Cookie: ASP.NET_SessionId=byvose45zmtbqy45d2a1jf2i; path=/; HttpOnly<br />Cache-Control: private<br />Content-Type: text/html; charset=utf-8<br />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<br />Host: dotnet.aspx.cc<br />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)<br />Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<br />Accept-Language: zh-cn,zh;q=0.5<br />Accept-Encoding: gzip,deflate<br />Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7<br />Keep-Alive: 300<br />Connection: keep-alive<br />POWERED-BY-MENGXIANHUI: Approve<br />Content-Type: application/xml; charset=UTF-8<br />Referer: http://www.meng_xian_hui.com:801/CrossDomainAjax/PreflightedRequests.html<br />Content-Length: 19<br />Origin: http://www.meng_xian_hui.com:801<br />Pragma: no-cache<br />Cache-Control: no-cache<br /><root>測試</root><br />HTTP/1.x 200 OK<br />Date: Sun, 10 Jan 2010 14:06:32 GMT<br />Server: Microsoft-IIS/6.0<br />X-Powered-By: ASP.NET<br />X-AspNet-Version: 2.0.50727<br />Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801<br />Set-Cookie: ASP.NET_SessionId=qs1c4urxywdbdx55u04pvual; path=/; HttpOnly<br />Cache-Control: private<br />Content-Type: text/html; charset=utf-8<br />Content-Length: 65
為什麼會這樣?細心的童鞋可能注意到了,在伺服器端有一行代碼 Response.AddHeader("Access-Control-Max-Age", "30"); 它是用來設定預檢的有效時間的,單位是秒。這一點要特別注意。