對於javascript程式員來說,發送ajax請求獲取後臺數據然後把數據和模板拼接成字元串渲染回DOM實現無刷新更新頁面這樣的操作可謂是輕車熟路。但眾所周知,ajax有一個不好,就是不能跨域傳輸數據,而跨域傳輸有時候又是必須用到的,比如我們可能需要調用第三方網站提供的某些API來獲取某些信息,提供 ...
對於javascript程式員來說,發送ajax請求獲取後臺數據然後把數據和模板拼接成字元串渲染回DOM實現無刷新更新頁面這樣的操作可謂是輕車熟路。但眾所周知,ajax有一個不好,就是不能跨域傳輸數據,而跨域傳輸有時候又是必須用到的,比如我們可能需要調用第三方網站提供的某些API來獲取某些信息,提供給我們網站的用戶。 例如,要開發一個天氣應用,你可能需要調用第三方的天氣API,這個時候就必然涉及到跨域請求數據,因為畢竟我們不可能為了開發一個天氣應用就自己搭建一個天氣API。在少數情況下,如果第三方網站的伺服器上設置了CORS機制,這時是可以直接用ajax發送請求的。但在大多數情況下,第三方網站都沒有這麼慷慨,因為涉及到安全問題,不可能允許任意人都能自由調用自己網站的介面。這個時候jsonp就應運而生了。 在HTML中有這樣一類標簽,它們都有一個src屬性,src屬性的值是一個鏈接,當標簽一被解析到DOM中,就會開始把src屬性中的鏈接指向的資源下載到本文檔,例如,設置了src的img標簽會自動從src屬性的鏈接中下載資源,下載的資源又會被瀏覽器解析成圖片,加到DOM中;設置了src屬性的iframe元素會從src鏈接里下載一張網頁;設置了src屬性的script元素也會自動從src鏈接里下載javascript代碼並執行,而這個過程中DOM本身也是沒有刷新的,更令人心動的是,src屬性的鏈接根本沒有同域的限制。這個原理就是實現JSONP的基石。 但是,我們想用它來實現跨域還很有難度。這個src屬性的元素的載入和一般的ajax請求有一個很大的不同,回想一下,在一個典型的ajax請求中,我們可以完全控制請求的過程,我們可以對指定的網頁實行open,可以設置請求頭,可以指定響應的MIMEtype,關鍵的是,我們可以從xhr對象的responseText屬性中獲取響應數據,然後拼接模板,渲染DOM。但在img, iframe, script這些標簽中呢,我們的控制權就被剝奪了,我們設置src屬性,瀏覽器負責發送請求,伺服器端返回的響應直接就被載入回DOM了,我們根本沒有插手修改數據的機會。 怎麼辦呢?這就像向遙遠的深空發射一艘飛船,當飛船遠離我們幾十光年的時候,我們就不太可能從地面對它發送實時控制信號了,更好的做法就是把指令提前寫在飛船上,讓它自動執行。 和飛船的例子一樣,一個典型的jsonp過程就是:創建一個script標簽,設置src屬性,這個src屬性中包括了目標API的地址,我們的查詢字元串querystring,querystring中最最重要的是我們的“指令”,因為script標簽src返回之後,我們並不能控制返回的結果,所以最好讓伺服器返回的時候自己執行我們想要執行的操作。這個“指令”也就是jsonp中的“p”了。 舉一個慄子: 1.我需要某個數據,比如就按照前面講的,天氣數據吧,於是我構造查詢url。
var script = document.createElement('script'); script.src = 'www.weather.fake/get/?province=hubei&city=wuhan&callback=instruction'; document.getElementsByTagName('head')[0].appendChild(script);
//先寫好指令,即回調 function instruction(data){ console.log(data); } //註意這裡的instruction就是我們告訴伺服器的“指令”。我們跟伺服器說,我需要province為hubei,city為wuhan的地方的天氣數據,但由於數據返回後我自己不能處理,所以你返回數據的時候自己處理好了,具體怎麼處理,我已經寫在名字叫做instruction的指令裡面了。
2.weather.fake的伺服器收到我的請求,從資料庫里一找,找到了數據。一看,還有個指令,於是它就執行instruction指令。說起來很高級,實際上也就是把返回的數據包裹在instruction函數裡面(jsonp的p,padding)。伺服器於是返回這樣一個東西:
//response.js instruction({3.伺服器端的writeheader設置和瀏覽器端的accept設置會保證返回的東西會被瀏覽器解釋成一個js文件,於是我們事先寫好的指令instruction函數就得到了執行,整個jsonp過程就完成了。 需要註意的幾點: 1.返回的js文件是在全局作用域執行的,所以你要保證你寫的回調函數instruction在全局作用域里。 2.這裡的callback=instruction。其中,callback只是一個普通的querystring,是你和伺服器事先約定的,不同API提供方,名字也不同,有的可能就叫cb,等等。至於instruction,你愛寫什麼寫什麼,但要保證和你寫的回調處理函數名字一致。我這裡為了方便理解,就寫instruction了。 餘論:ajax跨域,危險在哪裡? 有些人說,禁止ajax跨域,是為了防止攻擊者利用它向自己的伺服器發送敏感信息(例如cookie等等),這顯然是錯誤的。對於一個已經被註入攻擊代碼的網站,攻擊者如果只是想向自己的伺服器發送信息而已,就算不用ajax,也完全可以用img等標簽直接向自己的伺服器執行get請求發送數據,況且創建一個img標簽可比寫一個完整的ajax過程簡單多了,根本用不著興師動眾,也就是說,單向GET請求ajax並沒有優勢。 為什麼要限制ajax,因為它太強大了。看看CORS機制,它規定的是伺服器方面的接受白名單。也就是說,由伺服器來決定可以接受哪些客戶可以向它請求數據。說明跨域限制主要是為了保證伺服器端安全的。 一般能用src做到的,ajax都能做到,ajax能做到的src卻不一定能夠做到。例如src只能發起get請求,但ajax能發起任意類型的請求。 舉個慄子,某博客網站的刪除文章功能API可能必須要用DELETE方法發送請求才能執行,這個時候攻擊者如果只用src構造請求就無能為力了,但ajax卻可以輕易模擬用戶動作。這個時候禁止跨域就顯得很重要了。 具體過程:假設我的攻擊網站是www.evil.com。而一個允許CORS跨域傳輸ajax的博客網站是www.blog.com。該博客網站中,當用戶點擊了刪除按鈕時,就會向伺服器發送DELETE請求。具體為:www.blog.com/user/delete(?id=10000)(由於是DELETE方法,實際上querystring是不會附在url上的,這裡為了便於說明而已。)這個時候,假如用戶訪問我的evil.com網站,而我在我網站的腳本里寫ajax $.ajax({ url:'www.blog.com/user/delete', method:'delete', data:{id:10000}, }) 這樣是能成功的,因為當發送ajax時,會自動帶上用戶在www.blog.com的cookie,而請求方法又合法,所以完全能取得blog.com伺服器的信任,也就能不知不覺地刪除用戶在blog.com上寫的文章。
"city":"wuhan",
"weather":"cloudy"
})