同源策略 在瀏覽器的安全策略中“同源策略”非常如雷貫耳,說的是協議、功能變數名稱、埠相同則視為同源,功能變數名稱也可換成IP地址,不同源的頁面腳本不能獲取對方的數據。 要是想使用XMLHttpRequest或者常規的AJAX請求獲取另一個站點的數據,瀏覽器會告訴你“XXXX is not allowed by A ...
同源策略
在瀏覽器的安全策略中“同源策略”非常如雷貫耳,說的是協議、功能變數名稱、埠相同則視為同源,功能變數名稱也可換成IP地址,不同源的頁面腳本不能獲取對方的數據。
要是想使用XMLHttpRequest或者常規的AJAX請求獲取另一個站點的數據,瀏覽器會告訴你“XXXX is not allowed by Access-Control-Allow-Orign”.
因為同源策略的存在,防止了跨域訪問的安全問題,但同時也損失了方便獲取資源的便利。
跨域的src屬性
世事又非絕對,瀏覽器還是允許幾個元素跨域訪問外部資源的,如:<script>,<img>,<iframe>,也就是說,在html元素中擁有src屬性的元素是可以跨域訪問資源。
通過src屬性,img可以引用其它站點或圖床的圖片,大大降低本站的圖片持久。
通過src屬性,script可以引用CDN的JS文件,加速了瀏覽器的腳本文件的下載,跨域的數據獲取更加高效和方便。
通過src屬性,iframe可以嵌入其它站點的頁面,可以讓頁面的框架和可變內容分離,內容引用較為靈活,方便引用其它站點,雖然現在越來越不建議使用它。
正因為跨域訪問的存在,web世界才能更加的精彩。
而JSONP正式利用了script標簽的跨域能力。
JSONP
全名JSON with padding
就是通過約定,訪問跨域伺服器上數據的方法。
這種約定其實就是一個函數定義,並且具備數據參數的定義,由跨域伺服器的腳本或動態生成的腳本調用並且傳遞數據參數。
該函數稱之為“跨域回調函數”。
1.一個簡單的跨域腳本調用
本地伺服器上的一個腳本,直接引用了跨域伺服器上的腳本文件。
<html> <head> <title>test</title> </head> <body> <script src="http://localhost:3001/javascripts/jsonpsrc1.js"></script> </body> </html>
跨域伺服器上的腳本jsonpsrc1.js
alert('hi 我們不是一個域的哦');
運行後,瀏覽器訪問本地伺服器上的頁面,會彈出alert
2.跨域傳遞數據
在本地伺服器的腳本中,約定一個函數,名為jsonpCallback跨域回調函數交由跨域伺服器上的腳本調用。
註意,jsonpCallback跨域回調函數的定義必須是在引用跨域伺服器上的腳本之前。
<html> <head> <title>test</title> </head> <body> <script> function jsonpCallback(data){ alert(JSON.stringify(data,null,2)); } </script> <script src="http://localhost:3001/javascripts/jsonpsrc2.js"></script> </body> </html>
跨域伺服器上的腳本jsonpsrc2.js,按照約定的跨域回調函數名調用,並傳遞一個數據對象。
jsonpCallback({
name:'白色的海',
age:90
});
運行後,瀏覽器訪問本地伺服器上的頁面,會彈出alert,顯示跨域伺服器傳遞過來的數據。
3.動態跨域回調函數
通過上面的2種方法已經基本實現了JSONP的使用,但是還存在一個問題,就是必須要提前約定這個跨域回調函數的名字 。
每次都要按照跨域伺服器上的的回調函數名進行定義,極為不便。
那麼兩個跨域的腳本引用能否不綁定同一個函數名稱呢。
可以在本地伺服器腳本中任意定義跨域回調函數的名稱,將該函數名用過參數請求給跨域伺服器,在跨域伺服器後臺代碼上動態拼接生成回調函數的調用字元串並響應給請求方。
在這裡是用node.js+express的運行環境,並且以一個簡單的查詢航班信息的跨域請求進行簡單的實現。
在本地伺服器頁面腳本中定義了showFlightInfo跨域回調函數,用於顯示跨域伺服器返回的航班信息。
在其後的script標簽中對跨域伺服器進行了一個JS的請求,並將航班信息和跨域回調函數名帶過去。
<html> <head> <title>test</title> <link rel="stylesheet" href="/stylesheets/style.css"> </head> <body> <h1>航班信息</h1> <h1>某航</h1> <script> function showFlightInfo(data){ var flightNoEle = document.createElement('h4'); flightNoEle.innerHTML=data.flightNo; var fromEle = document.createElement('h4'); fromEle.innerHTML=data.from; var toEle = document.createElement('h4'); toEle.innerHTML=data.to; document.body.appendChild(flightNoEle); document.body.appendChild(fromEle); document.body.appendChild(toEle); } </script> <script src="http://localhost:3001/info/flight?flightNo=MU531&callbackFunc=showFlightInfo"></script> </body> </html>
跨域伺服器的後臺代碼中響應JS請求,接收航班號,生成航班信息,連同跨域回調函數名稱拼接成JS字元串後響應請求方。
router.get('/info/flight', function (req, res) { //生成航班信息,這裡直接拿請求過來的航班號寫一個航班信息對象 var data = { flightNo: req.query.flightNo, from: '北京', to: '上海'}; //獲取請求過來的跨域回調函數名稱 var callbackFunc = req.query.callbackFunc; //拼成一個JS字元串 var s = callbackFunc + '(' + JSON.stringify(data, null, 2) + ');'; console.log(s); //通過設置http的header,告知請求方響應的內容是JS腳本 res.setHeader('content-type', 'text/javascirpt;charset=utf-8'); //res.setHeader('content-language', 'zh-CN') res.write(s); res.end(); });
運行後,瀏覽器訪問本地伺服器上的頁面,會顯示跨域伺服器上傳遞過來的航班信息。
為了看得清,將跨域伺服器收到的請求和響應的內容輸出到控制台,方便查看。
第一行是收到的請求,後面的是對請求的響應,可以看到跨域服務端返回給請求方一個JS的腳本,其中的回調函數名稱是請求方定製的。
如此完成JSONP的基本實現。
不只是JSONP
通過跨域請求獲取數據本質上是利用了<script>標簽的src屬性,通過瀏覽器將跨域的腳本拉過來並同時執行。
那麼既然是拉過來執行,那和本頁面中的其它腳本的執行沒多大區分,那就當然也可以通過生成html元素和css的方式改變頁面的顯示內容,以實現更豐富的功能,比如廣告。
基於上面的航班信息,我要在其中顯示一塊廣告內容,內容來自於跨域伺服器。
通過<script>標簽引用跨域伺服器上對應的廣告資源地址
<html> <head> <title>test</title> <link rel="stylesheet" href="/stylesheets/style.css"> </head> <body> <h1>航班信息</h1> <script src="http://localhost:3001/ad"></script> <h1>某航</h1> <script> function showFlightInfo(data){ var flightNoEle = document.createElement('h4'); flightNoEle.innerHTML=data.flightNo; var fromEle = document.createElement('h4'); fromEle.innerHTML=data.from; var toEle = document.createElement('h4'); toEle.innerHTML=data.to; document.body.appendChild(flightNoEle); document.body.appendChild(fromEle); document.body.appendChild(toEle); } </script> <script src="http://localhost:3001/info/flight?flightNo=MU531&callbackFunc=showFlightInfo"></script> </body> </html>
跨域伺服器的後臺代碼中響應JS請求,生成一串使用document.write向頁面中生成html元素的字元串。
router.get('/ad', function (req, res) { //拼接一JS字元串,完成向html頁面中輸出html標記 var s = 'document.write(\'<div style="background-color:red;width:10rem;height:10rem">我是旅行社廣告</div>\');'; console.log(s); res.setHeader('content-type', 'text/javascirpt;charset=utf-8'); res.write(s); res.end(); });
運行後,瀏覽器訪問本地伺服器上的頁面,會在航班信息下方顯示紅色廣告位,而這個廣告位完全是跨域伺服器生成的並且包括樣式。
在此基礎上,可以做出很多效果,包括將頁面某一部分的生成交給專門的或者對應的業務伺服器上完成。