跨域問題相關知識詳解(原生js和jquery兩種方法實現jsonp跨域)
1、同源策略
同源策略(Same origin policy),它是由Netscape提出的一個著名的安全策略。同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現,現在所有支持JavaScript 的瀏覽器都會使用這個策略。
所謂同源,就是指兩個頁面具有相同的協議,主機(也常說域名),端口,三個要素缺一不可。
所謂同源策略,指的是瀏覽器對不同源的腳本或者文本的訪問方式進行的限制。即a.com 域名下的js無法操作b.com或是c.com域名下的對象。詳細見下表:
URL1 | URL2 | 說明 | 是否允許通信 |
---|---|---|---|
http://www.foo.com/js/a.js | http://www.foo.com/js/b.js | 協議、域名、端口都相同 | 允許 |
http://www.foo.com/js/a.js | http://www.foo.com:8888/js/b.js | 協議、域名相同,端口不同 | 不允許 |
https://www.foo.com/js/a.js | http://www.foo.com/js/b.js | 主機、域名相同,協議不同 | 不允許 |
http://www.foo.com/js/a.js | http://www.bar.com/js/b.js | 協議、端口相同,域名不同 | 不允許 |
http://www.foo.com/js/a.js | http://foo.com/js/b.js | 協議、端口相同,主域名相同,子域名不同 | 不允許 |
URL | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
同一域名下 | 允許 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js |
同一域名下不同文件夾 | 允許 |
http://www.a.com:8000/a.js http://www.a.com/b.js |
同一域名,不同端口 | 不允許 |
http://www.a.com/a.js https://www.a.com/b.js |
同一域名,不同協議 | 不允許 |
http://www.a.com/a.js http://70.32.92.74/b.js |
域名和域名對應ip | 不允許 |
http://www.a.com/a.js http://script.a.com/b.js |
主域相同,子域不同 | 不允許 |
http://www.a.com/a.js http://a.com/b.js |
同一域名,不同二級域名(同上) | 不允許(cookie這種情況下也不允許訪問) |
http://www.cnblogs.com/a.js http://www.a.com/b.js |
不同域名 | 不允許 |
- eg:當瀏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬於哪個頁面的,即檢查是否同源,只有和百度同源的腳本才會被執行。如果非同源,那麽在請求數據時,瀏覽器會在控制臺中報一個異常,提示拒絕訪問。
- 特別註意兩點:
- 第一,如果是協議和端口造成的跨域問題“前臺”是無能為力的;
- 第二:在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應著兩個域或兩個域是否在同一個ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。
前端所說的跨域一般指“前臺”處理跨域的辦法,後臺proxy這種方案牽涉到後臺配置,有興趣的可以參考yahoo的這篇文章:《JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls》
同源策略限制了不同源之間的交互,可我們平時文件中引用其他域名的js文件,css文件,圖片文件為何沒受到限制呢?同源策略限制的不同源之間的交互主要針對的是js中的XMLHttpRequest等請求,下面這些情況是完全不受同源策略限制的:
- 頁面中的鏈接,重定向以及表單提交是不會受到同源策略限制的。鏈接就不用說了,導航網站上的鏈接都是鏈接到其他站點的。而你在域名
www.foo.com
下面提交一個表單到www.bar.com
是完全可以的。 - 跨域資源嵌入是允許的,當然,瀏覽器限制了Javascript不能讀寫加載的內容。如前面提到的嵌入的
<script src="..."></script>,<img>,<link>,<iframe>等
。當然,如果要阻止iframe嵌入我們網站的資源(頁面或者js等),我們可以在web服務器加上一個X-Frame-Options DENY
頭部來限制。nginx就可以這樣設置add_header X-Frame-Options DENY;
。
互聯網的許多網站之間圖片相互盜鏈,A網站網頁的img.src直接鏈接到B網站的圖片地址,就是因為這個原因:<img>的src(獲取圖片),<link>的href(獲取css),<script>的src(獲取javascript)這三個都不符合同源策略,它們可以跨域獲取數據。因此,你可以直接從一些cdn上獲取jQuery,並且你網站上的圖片也隨時可能被別人盜用。
而我們的第一種跨域方法jsonp,就是因為<script>的src不符合同源策略而來的。
2、跨域問題-JSONP
Asynchronous JavaScript and XML (Ajax ) 是驅動新一代 Web 站點(流行術語為 Web 2.0 站點)的關鍵技術。Ajax 允許在不幹擾 Web 應用程序的顯示和行為的情況下在後臺進行數據檢索。使用 XMLHttpRequest
函數獲取數據,它是一種 API,允許客戶端 JavaScript 通過 HTTP 連接到遠程服務器。Ajax 也是許多 mashup 的驅動力,它可將來自多個地方的內容集成為單一Web 應用程序。
不過,由於受到瀏覽器的限制,該方法不允許跨域通信。
說到AJAX首先要思考的兩個問題,第一個是AJAX以何種格式來交換數據?第二個是跨域的需求如何解決?這兩個問題目前都有不同的解決方案,比如數據可以用自定義字符串或者用XML來描述,跨域可以通過服務器端代理來解決。但到目前為止最被推崇或者說首選的方案還是用JSON來傳數據,靠JSONP來跨域。
JSON(JavaScript Object Notation)和JSONP(JSON with Padding)雖然只有一個字母的差別,但其實他們根本不是一回事兒:JSON是一種數據交換格式,而JSONP是一種依靠開發人員的聰明才智創造出的一種非官方跨域數據交互協議。我們拿最近比較火的諜戰片來打個比方,JSON是地下黨們用來書寫和交換情報的“暗號”,而JSONP則是把用暗號書寫的情報傳遞給自己同誌時使用的接頭方式。看到沒?一個是描述信息的格式,一個是信息傳遞雙方約定的方法。
2.1 JSON
JSON:javaScript對象表示法(JavaScript Object Notation)
JSON is a subset of the object literal notation of JavaScript. Since JSON is a subset of JavaScript, it can be used in the language with no muss or fuss.
JSON是一種輕量級的數據交換格式。(json.org)
JSON是存儲和交換文本信息的語法,類似XML。它采用鍵值對的方式來組織,易於人們閱讀和編寫,同時也易於機器解析和生成。JSON是獨立於語言的,也就是說不管什麽語言,都可以解析json,只需要按照json的規則來就行。
JSON語法規則:JSON能夠以非常簡單的方式來描述數據結構,XML能做的它都能做,因此在跨平臺方面兩者完全不分伯仲。
(1)JSON只有兩種數據類型描述符,大括號{}和方括號[],其余英文冒號:是映射符,英文逗號,是分隔符,英文雙引號""是定義符。
(2)大括號{}用來描述一組“不同類型的無序鍵值對集合”(每個鍵值對可以理解為OOP的屬性描述),方括號[]用來描述一組“相同類型的有序數據集合”(可對應OOP的數組)。
(3)上述兩種集合中若有多個子項,則通過英文逗號,進行分隔。
(4)鍵值對以英文冒號:進行分隔,並且建議鍵名都加上英文雙引號"",以便於不同語言的解析。
(5)JSON內部常用數據類型有字符串、數字、布爾、日期、null 等,字符串必須用雙引號引起來,其余的都不用,日期類型比較特殊,建議如果客戶端沒有按日期排序功能需求的話,那麽把日期時間直接作為字符串傳遞就好,可以省去很多麻煩。
json解析的方法有兩種:eval()和parse()方法。————建議盡量使用JSON.parse方法來解析json裏的字符串。
JSON和XML比較:
(1)JSON的長度和XML格式比起來很短小;
(2)JSON讀寫的速度更快;
(3)JSON可以使用JavaScript內建的方法直接進行解析,轉換成JavaScipt對象,非常方便
JSON的優點:
(1)基於純文本,跨平臺傳遞極其簡單;
(2)Javascript原生支持,後臺語言幾乎全部支持;
(3)輕量級數據格式,占用字符數量極少,特別適合互聯網傳遞;
(4)可讀性強,容易編寫和解析;
JSON的缺點:
(1)JSON在服務端語言的支持不像XML那麽廣泛,不過JSON.org上提供很多語言的庫。
(2)如果你使用eval()來解析的話,會容易出現安全問題。
盡管如此,JSON的優點還是很明顯的。他是Ajax數據交互的很理想的數據格式。
2.2 JSONP
JSONP:JSON with Padding(填充式 JSON 或參數式 JSON),JSONP是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
2.2.1 JSONP是怎麽產生的?
1、一個眾所周知的問題,由於同源策略,Ajax直接請求普通文件存在跨域無權限訪問的問題,甭管你是靜態頁面、動態網頁、web服務、WCF,只要是跨域請求,一律不準;
2、不過我們又發現,Web頁面上調用js文件時則不受是否跨域的影響(不僅如此,我們還發現凡是擁有”src”這個屬性的標簽都擁有跨域的能力,比如<script>、<img>、<iframe>);
3、於是可以判斷,當前階段如果想通過純web端(ActiveX控件、服務端代理、屬於未來的HTML5之Websocket等方式不算)跨域訪問數據就只有一種可能,那就是在遠程服務器上設法把數據裝進js格式的文件裏,供客戶端調用和進一步處理;
4、恰巧我們已經知道有一種叫做JSON的純字符數據格式可以簡潔的描述復雜數據,更妙的是JSON還被js原生支持,所以在客戶端幾乎可以隨心所欲的處理這種格式的數據;
5、這樣子解決方案就呼之欲出了,web客戶端通過與調用腳本一模一樣的方式,來調用跨域服務器上動態生成的js格式文件(一般以JSON為後綴),顯而易見,服務器之所以要動態生成JSON文件,目的就在於把客戶端需要的數據裝入進去。
6、客戶端在對JSON文件調用成功之後,也就獲得了自己所需的數據,剩下的就是按照自己需求進行處理和展現了,這種獲取遠程數據的方式看起來非常像AJAX,但其實並不一樣。
7、為了便於客戶端使用數據,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然後服務端返回數據時會將這個callback參數作為函數名來包裹住JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了。
一句話總結:由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、端口)的資源,為了實現跨域請求,可以通過script標簽實現跨域請求,然後在服務端輸出JSON數據並執行回調函數,從而解決了跨域的數據請求。
2.2.2 JSONP的實現原理
JSONP借助了script標簽節點跨域訪問/獲取的特性。JSONP實現跨域請求的原理簡單的說,就是動態創建<script>
標簽,然後利用<script>
的src 不受同源策略約束來跨域獲取數據。
JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字一般是在請求中指定的。而數據就是傳入回調函數中的 JSON 數據。
a域名去聲明一個方法,b域名去調用這個方法,通過script標簽可以向不同域名提交http請求。
一、原生js實現jsonp
1.最簡單的一種,客戶端(a域名)的html文件
<!doctype html> <html lang="en"> <head></head> <body> <script type="text/javascript"> function jsonpCallback(result) { for(var i in result) { console.log(i+":"+result[i]);//循環輸出result的元素 } } </script> <script type="text/javascript" src="http://180.167.10.100/update/index.php?callback=jsonpCallback"></script> <!-- 傳遞固定參數的方式 --> <!-- <script type="text/javascript" src="http://180.167.10.100/update/index.php?os=Linux&version=3.3.3&callback=jsonpCallback"></script> --> </body> </html>
或者
<!doctype html> <html lang="en"> <head></head> <body> <script type="text/javascript"> function jsonpCallback(result) { //alert(result); for(var i in result) { console.log(i+":"+result[i]);//循環輸出result的元素 } } var JSONP=document.createElement("script"); JSONP.type="text/javascript"; JSONP.src="http://180.167.10.100/update/index.php?callback=jsonpCallback"; document.getElementsByTagName("head")[0].appendChild(JSONP); /*傳遞參數的方式*/ /*JSONP.src="http://180.167.10.100/update/index.php?os=Linux&version=3.3.3&callback=jsonpCallback";*/ </script> </body> </html>
服務端(b域名)的php代碼:
<?php //服務端返回JSON數據 //$arr=array(‘a‘=>1,‘b‘=>2,‘c‘=>3); $arr->IsLatestVersion = False; $arr->version = ‘3.4.3’; $arr->url = "http://172.30.28.18/update/releases/3.2.1.601/Windows/cdos-browser2_3.2.1.601.exe"; $result=json_encode($arr); //動態執行回調函數 $callback=$_GET[‘callback‘]; echo $callback."($result)"; ?>
2.在實際的項目中,我們的jsonp一般不會直接在html文件中實現,因為向服務器獲取數據的時機需要根據我們項目的實際需求來決定,所以一般需要動態的在我們所開發的模塊或某個js文件中創建script標簽以及傳遞相關參數(此參數也可能是項目過程中動態生成的),這時我們可以在客戶端(a域名)的js文件中如下實現:
// 得到查詢結果後的回調函數 function jsonpCallback(result) { for(var i in result) { console.log(i+":"+result[i]);//循環輸出result的元素 } } function getVersionInfo(){ var os = "Windows"; var version = "3.3.3"; var head = document.getElementsByTagName(‘head‘)[0]; var script = document.createElement(‘script‘);//創建script標簽,設置其屬性 var url = "http://180.167.10.100/update/index.php?os="+os+"&version="+version+"&callback=jsonpCallback"; script.type = "text/javascript"; script.setAttribute(‘src‘, url);//script.src= url;//提供jsonp服務的url地址 head.appendChild(script);// 把script標簽加入head,此時調用開始 } getVersionInfo(); //具體根據實際情況在合適位置調用即可
但是此時很可能出現報錯“jsonpCallback未定義”,原因其實很簡單,作為jsonp的回調函數,jsonpCallback必須是全局函數,而一般由於項目的模塊化和封裝我們的函數都是局部函數,此時我們必須將其全局化,可以將此回調函數jsonpCallback單拿出來放在html文件中,或者將其通過如下方法綁定到window對象上實現全局化:
(function(){ // 得到查詢結果後的回調函數 window[‘jsonpCallback‘] = function(data){ for(var i in result) { console.log(i+":"+result[i]);//循環輸出result的元素 } }; function getVersionInfo(){ var os = "Windows"; var version = "3.3.3"; var head = document.getElementsByTagName(‘head‘)[0]; var script = document.createElement(‘script‘);//創建script標簽,設置其屬性 var url = "http://180.167.10.100/update/index.php?os="+os+"&version="+version+"&callback=jsonpCallback"; script.type = "text/javascript"; script.setAttribute(‘src‘, url);//script.src= url;//提供jsonp服務的url地址 head.appendChild(script);// 把script標簽加入head,此時調用開始 } getVersionInfo();//具體根據實際情況在合適位置調用即可 })();
這樣jsonp的原理就很清楚了,首先在客戶端註冊一個callback(名字任意),然後動態創建script標簽(類似引入js文件的方式)通過src引入服務器端的php文件,同時將客戶端註冊的callback的名字傳給服務器,php文件載入成功後,服務器先生成我們需要的 json 數據,然後將其作為參數傳入我們在url參數中指定的函數,所以jsonp是需要服務器端的頁面進行相應的配合的。
二、jquery實現jsonp
jquery本身就支持JSONP。但是jsonp的方式只是針對get請求方式,不支持post請求。這也是Jsonp方式的局限性。
實現原理是一樣的,只不過我們不需要手動的插入script標簽以及定義回調函數。jquery會自動生成一個全局函數來替換callback=?中的問號,之後獲取到數據後又會自動銷毀,實際上就是起一個臨時代理函數的作用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。
jquery在處理jsonp類型的ajax時(還是忍不住吐槽,雖然jquery也把jsonp歸入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調函數並把數據取出來供success屬性方法來調用
。
;
2.
二、JSON和
知道jsonp跨域的原理後我們就可以用js動態生成script標簽來進行跨域操作了,而不用特意的手動的書寫那些script標簽。如果你的頁面使用jquery,那麽通過它封裝的方法就能很方便的來進行jsonp操作了。
跨域問題相關知識詳解(原生js和jquery兩種方法實現jsonp跨域)