AJAX實現跨域的幾種方法(一)

1. AJAX

AJAX(Asynchronous JavaScript and XML),意思就是用JavaScript執行異步網絡請求。
主要可以通過架設代理服務器,JSONP和CORS三種方案實現跨域
用JavaScript寫一個完整的AJAX代碼并不復雜,但是需要注意:AJAX請求是異步執行的,也就是說,要通過回調函數獲得響應。

創建ajax的過程一般如下:

  1. 創建XMLHttpRequest對象,也就是創建一個異步調用對象;
  2. 判斷XHR對象屬性;
  3. 創建一個新的HTTP請求,并指定該HTTP請求的方法、URL及驗證信息;
  4. 設置響應HTTP請求狀態變化的函數;
  5. 發送HTTP請求;
  6. 獲取異步調用返回的數據;
  7. 使用JavaScript和DOM實現局部刷新。

代碼。

var xmlhttp;
function createXMLHttpRequest () {
    xmlhttp = null;

    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
    }

    // 異步調用服務器段數據
    if (xmlhttp != null) {
        // 創建http請求
        xmlhttp.open('GET/POST', url, true);
        // 設置http請求狀態變化的函數
        xmlhttp.onreadystatechange = httpStateChange;
        // 發送請求
        xmlhttp.send(null);
    } else {
        console.log('不支持XHR');
    }
} 

// 響應HTTP請求狀態變化的函數
function httpStateChange () { //判斷異步調用是否完成
    if (xmlhttp.readyState == 4) {//readyState==4表示后臺處理完成了
        if (xmlhttp.status >= 200 && xmlhttp.status < 300 || xmlhttp.status == 304){
        //判斷異步調用是否成功,如果成功開始局部更新數據
            //code...
        } else{
            console.log("出錯狀態碼:" + xmlhttp.status + "出錯信息:" + xmlhttp.statusText);
        }
    }
}

對于低版本的IE,需要換一個ActiveXObject對象,如果你想把標準寫法和IE寫法混在一起,可以這么寫。

var request;
if (window.XMLHttpRequest) {
   request = new XMLHttpRequest();
} else {
  request = new ActiveXObject('Microsoft.XMLHTTP');
}

通過檢測window對象是否有XMLHttpRequest屬性來確定瀏覽器是否支持標準的XMLHttpRequest注意,不要根據瀏覽器的navigator.userAgent來檢測瀏覽器是否支持某個JavaScript特性,一是因為這個字符串本身可以偽造,二是通過IE版本判斷JavaScript特性將非常復雜。

當創建了XMLHttpRequest對象后,要先設置onreadystatechange
的回調函數。在回調函數中,通常我們只需通過readyState === 4判斷請求是否完成,如果已完成,再根據status判斷是否是一個成功的響應。

XMLHttpRequest對象的open()方法有3個參數,第一個參數指定是GET還是POST,第二個參數指定URL地址,第三個參數指定是否使用異步,默認是true,所以不用寫。注意,千萬不要把第三個參數指定為false,否則瀏覽器將停止響應,直到AJAX請求完成。如果這個請求耗時10秒,那么10秒內你會發現瀏覽器處于“假死”狀態。

最后調用send()方法才真正發送請求。GET請求不需要參數,POST請求需要把body部分以字符串或者FormData對象傳進去。

2. 跨域安全限制

因為瀏覽器的“同源策略”,協議、域名、端口號若有一個不同,則不能訪問。AJAX本身是不能跨域的,AJAX直接請求普通文件存在跨域無權限訪問的問題,只要是跨域請求,一律不準;但是配合后臺可以跨域。

因為同源策略限制的是瀏覽器但是對服務器不限制,服務器可以跨域。
那是不是用JavaScript無法請求外域(就是其他網站)的URL了呢?方法還是有的,大概有以下幾種。

2.1 CORS

CORS(Cross-Origin Resource Sharing,跨源資源共享)是W3C的一個草案,定義了在必須訪問跨域資源時,瀏覽器與服務器應該如何溝通。

CORS背后的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。

比如一個簡單的使用GET或者POST發送的請求,它沒有自定義頭部,而主體內容是text/plain。在發送該請求時,需要給它附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。下面是Origin頭部的一個示例。

Origin: http://www.nczonline.net

如果服務器認為這個請求可以接受,就在Access-Control-Allow-Origin頭部中回發相同的源信息(如果是公共資源,可以回發'*')。例如:

Access-Control-Allow-Origin: http://www.nczonline.net

如果沒有這個頭部,或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求。正常情況下,瀏覽器會處理請求。注意,請求進而響應都不包含cookie信息。

目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。

因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。

平時的ajax請求可能是這樣的:

<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "/damonare",true);
    xhr.send();
</script>

以上damonare部分是相對路徑,如果我們要使用CORS,相關Ajax代碼可能如下所示:

<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
    xhr.send();
</script>

代碼與之前的區別就在于相對路徑換成了其他域的絕對路徑,也就是你要跨域訪問的接口地址。

服務器端對于CORS的支持,主要就是通過設置Access-Control-Allow-Origin來進行的。如果瀏覽器檢測到相應的設置,就可以允許Ajax進行跨域的訪問。

關于CORS更多了解可以看下阮一峰大神的這一篇文章:跨域資源共享 CORS 詳解
BTW,阮一峰大神寫博客真是深入淺出...

2.2 圖像Ping

我們知道,一個網頁可以從任何網頁中加載圖像,不用擔心跨域不跨域。這也是在線廣告跟蹤瀏覽量的主要方式。我們也可以動態的創建圖像,使用它們的onloadonerror事件處理成西來確定是否接收到了響應。

動態創建圖像經常用于圖像Ping
圖像Ping是與服務器進行簡單、單向的跨域通信的一種方式。請求的數據是通過查詢字符串形式發送的,而響應可以是任意內容,但通常是像素圖或204響應。通過圖像Ping,瀏覽器得不到任何具體的數據,但通過偵聽loaderror事件,它能知道響應是什么時候收到的。

來看下面的例子。

var img = new Image();
img.onload = img.onerror = function () {
    console.log('Done');
};
img.src = 'http://www.example.com/test?name=Nico';

這里創建了一個Image的實例,然后將onloadonerror事件處理程序指定為同一個函數。這樣無論是什么響應,只要請求完成,就能得到通知。請求從設置src屬性那一刻開始,而這個例子在請求中發送了一個name參數。

圖像Ping最常用于跟蹤用戶點擊頁面或動態廣告曝光次數。
圖像Ping有兩個主要的缺點:

  • 只能發送GET請求。
  • 無法訪問服務器的響應文本。

因此,圖像Ping只能用于瀏覽器與服務器間的單向通信。

2.3 JSONP

JSONP是JSON with padding(填充式JSON或參數式JSON)的簡寫,是應用JSON的一種新方法。JSONP與JSON看起來差不多,只不過是被包含在函數調用中的JSON,如下。

callback({'name': 'Azure'});

JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字一般是在請求中指定的,而數據就是傳入回調參數中JSON數據。下面是一個典型的JSONP請求。

http://freegeoip.net/json/?callback=handleResponse

這個URL是在請求一個JSONP地理定位服務。通過查詢字符串來指定JSONP服務的回調參數是很常見的,就像上面的URL所示,這里指定的回調函數的名字叫handleResponse()

JSONP是通過動態<script>元素來使用的,使用時可以為src屬性指定一個跨域URL。
這里的<scriot>元素與<img>元素類似,都有能力不受限制的從其他域加載資源。因為JSONP是有效的JS代碼,所以在請求完成后,即在JSONP響應加載到頁面中以后,就會立即執行。來看一個例子。

function handleResponse (response) {
    console.log('u r 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);

這個例子通過查詢地理定位服務來顯示IP地址和地理位置信息。

JSONP之所以在開發人員中極為流行,主要原因是它非常簡單易用。與圖像Ping相比,它的優點在于能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信。不過,JSONP也有兩點不足。

首先,安全性問題。JSONP是從其他域中加載代碼執行。如果其他域不安全,很可能會在響應中夾帶一些惡意代碼,而此時除了完全放棄JSONP調用之外,沒有辦法追究。因此在使用不是自己運維的Web服務時,一定得保證它安全可靠。
其次,要確定JSONP請求是否失敗并不容易

CORS和JSONP對比

  • JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。
  • 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。
  • SONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS。

關于JSONP,曾在JSONP跨域詳解一文中詳述。


下一篇會寫寫Comet和Web Sockets

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容