前言
先後端數據交互常常會碰到請求跨域,什麼是跨域,以及有哪幾種跨域方式,這是本文要探討的內容。
本文完整的源代碼請猛戳github博客,紙上得來終覺淺,建議你們動手敲敲代碼。
1、什麼是跨域?
1.什麼是同源策略及其限制內容?
同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。
原文地址:前端跨域總結javascript
博主博客地址:Damonare的我的博客php
相信每個前端er對於跨域這兩個字都不會陌生,在實際項目中應用也是比較多的。但跨域方法的多種多樣實在讓人應接不暇。老規矩,碰到這種狀況,就只能本身總結一篇博客,做爲記錄。css
跨域一詞從字面意思看,就是跨域名嘛,但實際上跨域的範圍絕對不止那麼狹隘。具體概念以下:只要協議、域名、端口有任何一個不一樣,都被看成是不一樣的域。之因此會產生跨域這個問題呢,其實也很容易想明白,要是隨便引用外部文件,不一樣標籤下的頁面引用相似的彼此的文件,瀏覽器很容易懵逼的,安全也得不到保障了就。什麼事,都是安全第一嘛。但在安全限制的同時也給注入iframe或是ajax應用上帶來了很多麻煩。因此咱們要經過一些方法使本域的js可以操做其餘域的頁面對象或者使其餘域的js能操做本域的頁面對象(iframe之間)。下面是具體的跨域狀況詳解:html
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 主域相同,子域不一樣 不容許(cookie這種狀況下也不容許訪問) http://www.a.com/a.js http://a.com/b.js 同一域名,不一樣二級域名(同上) 不容許(cookie這種狀況下也不容許訪問) http://www.cnblogs.com/a.js http://www.a.com/b.js 不一樣域名 不容許複製代碼
這裏咱們須要注意兩點:前端
前面說過了,瀏覽器有一個同源策略,其限制之一是不能經過ajax的方法去請求不一樣源中的文檔。第二個限制是瀏覽器中不一樣域的框架之間是不能進行js的交互操做的。不一樣的框架之間是能夠獲取window對象的,但卻沒法獲取相應的屬性和方法。好比,有一個頁面,它的地址是www.damonare.cn/a.html , 在這個頁面裏面有一個iframe,它的src是damonare.cn/b.html, 很顯然,這個頁面與它裏面的iframe框架是不一樣域的,因此咱們是沒法經過在頁面中書寫js代碼來獲取iframe中的東西的:vue
<script type="text/javascript">
function test(){
var iframe = document.getElementById('ifame');
var win = iframe.contentWindow;//能夠獲取到iframe裏的window對象,但該window對象的屬性和方法幾乎是不可用的
var doc = win.document;//這裏獲取不到iframe裏的document對象
var name = win.name;//這裏一樣獲取不到window對象的name屬性
}
</script>
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe>複製代碼
這個時候,document.domain就能夠派上用場了,咱們只要把www.damonare.cn/a.html 和 damonare.cn/b.html 這兩個頁面的document.domain都設成相同的域名就能夠了。但要注意的是,document.domain的設置是有限制的,咱們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。html5
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe> <script type="text/javascript"> document.domain = 'damonare.cn';//設置成主域 function test(){ alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象 } </script>複製代碼
<script type="text/javascript"> document.domain = 'damonare.cn';//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同 </script>複製代碼
修改document.domain的方法只適用於不一樣子域的框架間的交互。java
由於父窗口能夠對iframe進行URL讀寫,iframe也能夠讀寫父窗口的URL,URL有一部分被稱爲hash,就是#號及其後面的字符,它通常用於瀏覽器錨點定位,Server端並不關心這部分,應該說HTTP請求過程當中不會攜帶hash,因此這部分的修改不會產生HTTP請求,可是會產生瀏覽器歷史記錄。此方法的原理就是改變URL的hash部分來進行雙向通訊。每一個window經過改變其餘 window的location來發送消息(因爲兩個頁面不在同一個域下IE、Chrome不容許修改parent.location.hash的值,因此要藉助於父窗口域名下的一個代理iframe),並經過監聽本身的URL的變化來接收消息。這個方式的通訊會形成一些沒必要要的瀏覽器歷史記錄,並且有些瀏覽器不支持onhashchange事件,須要輪詢來獲知URL的改變,最後,這樣作也存在缺點,諸如數據直接暴露在了url中,數據容量和類型都有限等。下面舉例說明:node
假如父頁面是baidu.com/a.html,iframe嵌入的頁面爲google.com/b.html(此處省略了域名等url屬性),要實現此兩個頁面間的通訊能夠經過如下方法。jquery
b.html傳送數據到a.html,因爲兩個頁面不在同一個域下IE、Chrome不容許修改parent.location.hash的值,因此要藉助於父窗口域名下的一個代理iframe
b.html頁面的關鍵代碼以下:
try { parent.location.hash = 'data'; } catch (e) { // ie、chrome的安全機制沒法修改parent.location.hash, var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = "http://www.baidu.com/proxy.html#data"; document.body.appendChild(ifrproxy); }複製代碼
proxy.html頁面的關鍵代碼以下 :
//由於parent.parent(即baidu.com/a.html)和baidu.com/proxy.html屬於同一個域,因此能夠改變其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1);複製代碼
高級瀏覽器Internet Explorer 8+, chrome,Firefox , Opera 和 Safari 都將支持這個功能。這個功能主要包括接受信息的"message"事件和發送消息的"postMessage"方法。好比damonare.cn域的A頁面經過iframe嵌入了一個google.com域的B頁面,能夠經過如下方法實現A和B的通訊
A頁面經過postMessage方法發送消息:
window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = "http://www.google.com"; ifr.contentWindow.postMessage('hello world!', targetOrigin); };複製代碼
postMessage的使用方法:
otherWindow.postMessage(message, targetOrigin);
B頁面經過message事件監聽並接受消息:
var onmessage = function (event) { var data = event.data;//消息 var origin = event.origin;//消息來源地址 var source = event.source;//源Window對象 if(origin=="http://www.baidu.com"){ console.log(data);//hello world! } }; if (typeof window.addEventListener != 'undefined') { window.addEventListener('message', onmessage, false); } else if (typeof window.attachEvent != 'undefined') { //for ie window.attachEvent('onmessage', onmessage); }複製代碼
同理,也能夠B頁面發送消息,而後A頁面監聽並接受消息。
剛纔說的這幾種都是雙向通訊的,即兩個iframe,頁面與iframe或是頁面與頁面之間的,下面說幾種單項跨域的(通常用來獲取數據),由於經過script標籤引入的js是不受同源策略的限制的。因此咱們能夠經過script標籤引入一個js或者是一個其餘後綴形式(如php,jsp等)的文件,此文件返回一個js函數的調用。
好比,有個a.html頁面,它裏面的代碼須要利用ajax獲取一個不一樣域上的json數據,假設這個json數據地址是damonare.cn/data.php, 那麼a.html中的代碼就能夠這樣:
<script type="text/javascript">
function dosomething(jsondata){
//處理得到的json數據
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>複製代碼
咱們看到獲取數據的地址後面還有一個callback參數,按慣例是用這個參數名,可是你用其餘的也同樣。固然若是獲取數據的jsonp地址頁面不是你本身能控制的,就得按照提供數據的那一方的規定格式來操做了。
由於是當作一個js文件來引入的,因此damonare.cn/data.php 返回的必須是一個能執行的js文件,因此這個頁面的php代碼多是這樣的(必定要和後端約定好哦):
<?php
$callback = $_GET['callback'];//獲得回調函數名 $data = array('a','b','c');//要返回的數據 echo $callback.'('.json_encode($data).')';//輸出 ?>複製代碼
最終,輸出結果爲:dosomething(['a','b','c']);
若是你的頁面使用jquery,那麼經過它封裝的方法就能很方便的來進行jsonp操做了。
<script type="text/javascript"> $.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){ //處理得到的json數據 }); </script>複製代碼
jquery會自動生成一個全局函數來替換callback=?中的問號,以後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的做用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。
CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功仍是失敗。目前,全部瀏覽器都支持該功能,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 詳解
CORS和JSONP對比
JSONP只能實現GET請求,而CORS支持全部類型的HTTP請求。
使用CORS,開發者可使用普通的XMLHttpRequest發起請求和得到數據,比起JSONP有更好的錯誤處理。
JSONP主要被老的瀏覽器支持,它們每每不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS)。
CORS與JSONP相比,無疑更爲先進、方便和可靠。
window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。
好比:咱們在任意一個頁面輸入
window.name = "My window's name"; setTimeout(function(){ window.location.href = "http://damonare.cn/"; },1000)複製代碼
進入damonare.cn頁面後咱們再檢測再檢測 window.name :
window.name; // My window's name複製代碼
能夠看到,若是在一個標籤裏面跳轉網頁的話,咱們的 window.name 是不會改變的。
基於這個思想,咱們能夠在某個頁面設置好 window.name 的值,而後跳轉到另一個頁面。在這個頁面中就能夠獲取到咱們剛剛設置的 window.name 了。
因爲安全緣由,瀏覽器始終會保持 window.name 是string 類型。
一樣這個方法也能夠應用到和iframe的交互來:
好比:個人頁面(damonare.cn/index.html)…
<iframe id="iframe" src="http://www.google.com/iframe.html"></iframe>複製代碼
在 iframe.html 中設置好了 window.name 爲咱們要傳遞的字符串。
咱們在 index.html 中寫了下面的代碼:
var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { data = iframe.contentWindow.name; };複製代碼
Boom!報錯!確定的,由於兩個頁面不一樣源嘛,想要解決這個問題能夠這樣幹:
var iframe = document.getElementById('iframe'); var data = ''; iframe.onload = function() { iframe.onload = function(){ data = iframe.contentWindow.name; } iframe.src = 'about:blank'; };複製代碼
或者將裏面的 about:blank 替換成某個同源頁面(about:blank,javascript: 和 data: 中的內容,繼承了載入他們的頁面的源。)
這種方法與 document.domain 方法相比,放寬了域名後綴要相同的限制,能夠從任意頁面獲取 string 類型的數據。
其它諸如中間件跨域,服務器代理跨域,Flash URLLoader跨域,動態建立script標籤(簡化版本的jsonp)不做討論。
參考文章:
九種跨域方式實現原理(完整版)
前言
先後端數據交互常常會碰到請求跨域,什麼是跨域,以及有哪幾種跨域方式,這是本文要探討的內容。
本文完整的源代碼請猛戳github博客,紙上得來終覺淺,建議你們動手敲敲代碼。
1、什麼是跨域?
1.什麼是同源策略及其限制內容?
同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。
同源策略限制內容有:
- Cookie、LocalStorage、IndexedDB 等存儲性內容
- DOM 節點
- AJAX 請求發送後,結果被瀏覽器攔截了
可是有三個標籤是容許跨域加載資源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
2.常見跨域場景
當協議、子域名、主域名、端口號中任意一個不相同時,都算做不一樣域。不一樣域之間相互請求資源,就算做「跨域」。常見跨域場景以下圖所示:
特別說明兩點:
第一:若是是協議和端口形成的跨域問題「前臺」是無能爲力的。
第二:在跨域問題上,僅僅是經過「URL的首部」來識別而不會根據域名對應的IP地址是否相同來判斷。「URL的首部」能夠理解爲「協議, 域名和端口必須匹配」。
這裏你或許有個疑問:請求跨域了,那麼請求到底發出去沒有?
跨域並非請求發不出去,請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了。你可能會疑問明明經過表單的方式能夠發起跨域請求,爲何 Ajax 就不會?由於歸根結底,跨域是爲了阻止用戶讀取到另外一個域名下的內容,Ajax 能夠獲取響應,瀏覽器認爲這不安全,因此攔截了響應。可是表單並不會獲取新的內容,因此能夠發起跨域請求。同時也說明了跨域並不能徹底阻止 CSRF,由於請求畢竟是發出去了。
2、跨域解決方案
1.jsonp
1) JSONP原理
利用
<script>
標籤沒有跨域限制的漏洞,網頁能夠獲得從其餘來源動態產生的 JSON 數據。JSONP請求必定須要對方的服務器作支持才能夠。2) JSONP和AJAX對比
JSONP和AJAX相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式。但AJAX屬於同源策略,JSONP屬於非同源策略(跨域請求)
3) JSONP優缺點
JSONP優勢是簡單兼容性好,可用於解決主流瀏覽器的跨域數據訪問的問題。缺點是僅支持get方法具備侷限性,不安全可能會遭受XSS攻擊。
4) JSONP的實現流程
- 聲明一個回調函數,其函數名(如show)當作參數值,要傳遞給跨域請求數據的服務器,函數形參爲要獲取目標數據(服務器返回的data)。
- 建立一個
<script>
標籤,把那個跨域的API數據接口地址,賦值給script的src,還要在這個地址中向服務器傳遞該函數名(能夠經過問號傳參:?callback=show)。- 服務器接收到請求後,須要進行特殊的處理:把傳遞進來的函數名和它須要給你的數據拼接成一個字符串,例如:傳遞進去的函數名是show,它準備好的數據是
show('我不愛你')
。- 最後服務器把準備的數據經過HTTP協議返回給客戶端,客戶端再調用執行以前聲明的回調函數(show),對返回的數據進行操做。
在開發中可能會遇到多個 JSONP 請求的回調函數名是相同的,這時候就須要本身封裝一個 JSONP函數。
// index.html function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { ...params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src = `${url}?${arrs.join('&')}` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: 'Iloveyou' }, callback: 'show' }).then(data => { console.log(data) }) 複製代碼
上面這段代碼至關於向
http://localhost:3000/say?wd=Iloveyou&callback=show
這個地址請求數據,而後後臺返回show('我不愛你')
,最後會運行show()這個函數,打印出'我不愛你'// server.js let express = require('express') let app = express() app.get('/say', function(req, res) { let { wd, callback } = req.query console.log(wd) // Iloveyou console.log(callback) // show res.end(`${callback}('我不愛你')`) }) app.listen(3000) 複製代碼
5) jQuery的jsonp形式
JSONP都是GET和異步請求的,不存在其餘的請求方式和同步請求,且jQuery默認就會給JSONP的請求清除緩存。
$.ajax({ url:"http://crossdomain.com/jsonServerResponse", dataType:"jsonp", type:"get",//能夠省略 jsonpCallback:"show",//->自定義傳遞給服務器的函數名,而不是使用jQuery自動生成的,可省略 jsonp:"callback",//->把傳遞函數名的那個形參callback,可省略 success:function (data){ console.log(data);} }); 複製代碼
2.cors
CORS 須要瀏覽器和後端同時支持。IE 8 和 9 須要經過 XDomainRequest 來實現。
瀏覽器會自動進行 CORS 通訊,實現 CORS 通訊的關鍵是後端。只要後端實現了 CORS,就實現了跨域。
服務端設置 Access-Control-Allow-Origin 就能夠開啓 CORS。 該屬性表示哪些域名能夠訪問資源,若是設置通配符則表示全部網站均可以訪問資源。
雖然設置 CORS 和前端沒什麼關係,可是經過這種方式解決跨域問題的話,會在發送請求時出現兩種狀況,分別爲簡單請求和複雜請求。
1) 簡單請求
只要同時知足如下兩大條件,就屬於簡單請求
條件1:使用下列方法之一:
- GET
- HEAD
- POST
條件2:Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
請求中的任意 XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器; XMLHttpRequestUpload 對象可使用 XMLHttpRequest.upload 屬性訪問。
2) 複雜請求
不符合以上條件的請求就確定是複雜請求了。 複雜請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求,該請求是 option 方法的,經過該請求來知道服務端是否容許跨域請求。
咱們用
PUT
向後臺請求時,屬於複雜請求,後臺需作以下配置:// 容許哪一個方法訪問我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 預檢的存活時間 res.setHeader('Access-Control-Max-Age', 6) // OPTIONS請求不作任何處理 if (req.method === 'OPTIONS') { res.end() } // 定義後臺返回的內容 app.put('/getData', function(req, res) { console.log(req.headers) res.end('我不愛你') }) 複製代碼
接下來咱們看下一個完整複雜請求的例子,而且介紹下CORS請求相關的字段
// index.html let xhr = new XMLHttpRequest() document.cookie = 'name=xiamen' // cookie不能跨域 xhr.withCredentials = true // 前端設置是否帶cookie xhr.open('PUT', 'http://localhost:4000/getData', true) xhr.setRequestHeader('name', 'xiamen') xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.response) //獲得響應頭,後臺需設置Access-Control-Expose-Headers console.log(xhr.getResponseHeader('name')) } } } xhr.send() 複製代碼
//server1.js let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000); 複製代碼
//server2.js let express = require('express') let app = express() let whitList = ['http://localhost:3000'] //設置白名單 app.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // 設置哪一個源能夠訪問我 res.setHeader('Access-Control-Allow-Origin', origin) // 容許攜帶哪一個頭訪問我 res.setHeader('Access-Control-Allow-Headers', 'name') // 容許哪一個方法訪問我 res.setHeader('Access-Control-Allow-Methods', 'PUT') // 容許攜帶cookie res.setHeader('Access-Control-Allow-Credentials', true) // 預檢的存活時間 res.setHeader('Access-Control-Max-Age', 6) // 容許返回的頭 res.setHeader('Access-Control-Expose-Headers', 'name') if (req.method === 'OPTIONS') { res.end() // OPTIONS請求不作任何處理 } } next() }) app.put('/getData', function(req, res) { console.log(req.headers) res.setHeader('name', 'jw') //返回一個響應頭,後臺需設置 res.end('我不愛你') }) app.get('/getData', function(req, res) { console.log(req.headers) res.end('我不愛你') }) app.use(express.static(__dirname)) app.listen(4000) 複製代碼
上述代碼由
http://localhost:3000/index.html
向http://localhost:4000/
跨域請求,正如咱們上面所說的,後端是實現 CORS 通訊的關鍵。3.postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是爲數很少能夠跨域操做的window屬性之一,它可用於解決如下方面的問題:
- 頁面和其打開的新窗口的數據傳遞
- 多窗口之間消息傳遞
- 頁面與嵌套的iframe消息傳遞
- 上面三個場景的跨域數據傳遞
postMessage()方法容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 將要發送到其餘 window的數據。
- targetOrigin:經過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串"*"(表示無限制)或者一個URI。在發送消息的時候,若是目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那麼消息就不會被髮送;只有三者徹底匹配,消息纔會被髮送。
- transfer(可選):是一串和message 同時傳遞的 Transferable 對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權。
接下來咱們看個例子:
http://localhost:3000/a.html
頁面向http://localhost:4000/b.html
傳遞「我愛你」,而後後者傳回"我不愛你"。// a.html <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加載完觸發一個事件 //內嵌在http://localhost:3000/a.html <script> function load() { let frame = document.getElementById('frame') frame.contentWindow.postMessage('我愛你', 'http://localhost:4000') //發送數據 window.onmessage = function(e) { //接受返回數據 console.log(e.data) //我不愛你 } } </script> 複製代碼
// b.html window.onmessage = function(e) { console.log(e.data) //我愛你 e.source.postMessage('我不愛你', e.origin) } 複製代碼
4.websocket
Websocket是HTML5的一個持久化的協議,它實現了瀏覽器與服務器的全雙工通訊,同時也是跨域的一種解決方案。WebSocket和HTTP都是應用層協議,都基於 TCP 協議。可是 WebSocket 是一種雙向通訊協議,在創建鏈接以後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket 在創建鏈接時須要藉助 HTTP 協議,鏈接創建好了以後 client 與 server 之間的雙向通訊就與 HTTP 無關了。
原生WebSocket API使用起來不太方便,咱們使用
Socket.io
,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。咱們先來看個例子:本地文件socket.html向
localhost:3000
發生數據和接受數據// socket.html <script> let socket = new WebSocket('ws://localhost:3000'); socket.onopen = function () { socket.send('我愛你');//向服務器發送數據 } socket.onmessage = function (e) { console.log(e.data);//接收服務器返回的數據 } </script> 複製代碼
// server.js let express = require('express'); let app = express(); let WebSocket = require('ws');//記得安裝ws let wss = new WebSocket.Server({port:3000}); wss.on('connection',function(ws) { ws.on('message', function (data) { console.log(data); ws.send('我不愛你') }); }) 複製代碼
5. Node中間件代理(兩次跨域)
實現原理:同源策略是瀏覽器須要遵循的標準,而若是是服務器向服務器請求就無需遵循同源策略。 代理服務器,須要作如下幾個步驟:
- 接受客戶端請求 。
- 將請求 轉發給服務器。
- 拿到服務器 響應 數據。
- 將 響應 轉發給客戶端。
咱們先來看個例子:本地文件index.html文件,經過代理服務器
http://localhost:3000
向目標服務器http://localhost:4000
請求數據。// index.html(http://127.0.0.1:5500) <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> $.ajax({ url: 'http://localhost:3000', type: 'post', data: { name: 'xiamen', password: '123456' }, contentType: 'application/json;charset=utf-8', success: function(result) { console.log(result) // {"title":"fontend","password":"123456"} }, error: function(msg) { console.log(msg) } }) </script> 複製代碼
// server1.js 代理服務器(http://localhost:3000) const http = require('http') // 第一步:接受客戶端請求 const server = http.createServer((request, response) => { // 代理服務器,直接和瀏覽器直接交互,須要設置CORS 的首部字段 response.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': 'Content-Type' }) // 第二步:將請求轉發給服務器 const proxyRequest = http .request( { host: '127.0.0.1', port: 4000, url: '/', method: request.method, headers: request.headers }, serverResponse => { // 第三步:收到服務器的響應 var body = '' serverResponse.on('data', chunk => { body += chunk }) serverResponse.on('end', () => { console.log('The data is ' + body) // 第四步:將響應結果轉發給瀏覽器 response.end(body) }) } ) .end() }) server.listen(3000, () => { console.log('The proxyServer is running at http://localhost:3000') }) 複製代碼
// server2.js(http://localhost:4000) const http = require('http') const data = { title: 'fontend', password: '123456' } const server = http.createServer((request, response) => { if (request.url === '/') { response.end(JSON.stringify(data)) } }) server.listen(4000, () => { console.log('The server is running at http://localhost:4000') }) 複製代碼
上述代碼通過兩次跨域,值得注意的是瀏覽器向代理服務器發送請求,也遵循同源策略,最後在index.html文件打印出
{"title":"fontend","password":"123456"}
6.nginx反向代理
實現原理相似於Node中間件代理,須要你搭建一箇中轉nginx服務器,用於轉發請求。
使用nginx反向代理實現跨域,是最簡單的跨域方式。只須要修改nginx的配置便可解決跨域問題,支持全部瀏覽器,支持session,不須要修改任何代碼,而且不會影響服務器性能。
實現思路:經過nginx配置一個代理服務器(域名與domain1相同,端口不一樣)作跳板機,反向代理訪問domain2接口,而且能夠順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登陸。
先下載nginx,而後將nginx目錄下的nginx.conf修改以下:
// proxy服務器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裏域名 index index.html index.htm; # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用 add_header Access-Control-Allow-Origin http://www.domain1.com; #當前端只跨域不帶cookie時,可爲* add_header Access-Control-Allow-Credentials true; } } 複製代碼
最後經過命令行
nginx -s reload
啓動nginx// index.html var xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問nginx中的代理服務器 xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); xhr.send(); 複製代碼
// server.js var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前臺寫cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:腳本沒法讀取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); 複製代碼
7.window.name + iframe
window.name屬性的獨特之處:name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。
其中a.html和b.html是同域的,都是
http://localhost:3000
;而c.html是http://localhost:4000
// a.html(http://localhost:3000/b.html) <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe> <script> let first = true // onload事件會觸發2次,第1次加載跨域頁,並留存數據於window.name function load() { if(first){ // 第1次onload(跨域頁)成功後,切換到同域代理頁面 let iframe = document.getElementById('iframe'); iframe.src = 'http://localhost:3000/b.html'; first = false; }else{ // 第2次onload(同域b.html頁)成功後,讀取同域window.name中數據 console.log(iframe.contentWindow.name); } } </script> 複製代碼
b.html爲中間代理頁,與a.html同域,內容爲空。
// c.html(http://localhost:4000/c.html) <script> window.name = '我不愛你' </script> 複製代碼
總結:經過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。
8.location.hash + iframe
實現原理: a.html欲與c.html跨域相互通訊,經過中間頁b.html來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。
具體實現步驟:一開始a.html給c.html傳一個hash值,而後c.html收到hash值後,再把hash值傳遞給b.html,最後b.html將結果放到a.html的hash值中。 一樣的,a.html和b.html是同域的,都是
http://localhost:3000
;而c.html是http://localhost:4000
// a.html <iframe src="http://localhost:4000/c.html#iloveyou"></iframe> <script> window.onhashchange = function () { //檢測hash的變化 console.log(location.hash); } </script> 複製代碼
// b.html <script> window.parent.parent.location.hash = location.hash //b.html將結果放到a.html的hash值中,b.html可經過parent.parent訪問a.html頁面 </script> 複製代碼
// c.html console.log(location.hash); let iframe = document.createElement('iframe'); iframe.src = 'http://localhost:3000/b.html#idontloveyou'; document.body.appendChild(iframe); 複製代碼
9.document.domain + iframe
該方式只能用於二級域名相同的狀況下,好比
a.test.com
和b.test.com
適用於該方式。 只須要給頁面添加document.domain ='test.com'
表示二級域名都相同就能夠實現跨域。實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。
咱們看個例子:頁面
a.zf1.cn:3000/a.html
獲取頁面b.zf1.cn:3000/b.html
中a的值// a.html <body> helloa <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe> <script> document.domain = 'zf1.cn' function load() { console.log(frame.contentWindow.a); } </script> </body> 複製代碼
// b.html <body> hellob <script> document.domain = 'zf1.cn' var a = 100; </script> </body> 複製代碼
3、總結
- CORS支持全部類型的HTTP請求,是跨域HTTP請求的根本解決方案
- JSONP只支持GET請求,JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。
- 無論是Node中間件代理仍是nginx反向代理,主要是經過同源策略對服務器不加限制。
- 平常工做中,用得比較多的跨域方案是cors和nginx反向代理
參考文章
前端解決跨域的九種方法
什麼是跨域?
跨域是指一個域下的文檔或腳本試圖去請求另外一個域下的資源,這裏跨域是廣義的。
廣義的跨域:
一、
資源跳轉:A連接、重定向、表單提交
二、資源嵌入: <link>、<script>
其實咱們一般所說的跨域是狹義的,是由瀏覽器同源策略限制的一類請求場景。
什麼是同源策略?
同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。同源策略限制如下幾種行爲:
一、Cookie、LocalStorage 和 IndexDB 沒法讀取 二、 DOM 和 Js對象沒法得到 三、 AJAX 請求不能發送
常見跨域場景
URL 說明 是否容許通訊 http://www.demo.com/a.js http://www.demo.com/b.js 同一域名,不一樣文件或路徑 容許 http://www.demo.com/lab/c.js http://www.demo.com:8000/a.js http://www.demo.com/b.js 同一域名,不一樣端口 不容許 http://www.demo.com/a.js https://www.demo.com/b.js 同一域名,不一樣協議 不容許 http://www.demo.com/a.js http://127.0.0.1/b.js 域名和域名對應相同ip 不容許 http://www.demo.com/a.js http://x.demo.com/b.js 主域相同,子域不一樣 不容許 http://demo.com/c.js http://www.demo1.com/a.js http://www.demo2.com/b.js 不一樣域名 不容許
跨域解決方案
一、 經過jsonp跨域
二、 document.domain + iframe跨域
三、 location.hash + iframe
四、 window.name + iframe跨域
五、 postMessage跨域
六、 跨域資源共享(CORS)
七、 nginx代理跨域
八、 nodejs中間件代理跨域
九、 WebSocket協議跨域1、 經過jsonp跨域
一般爲了減輕web服務器的負載,咱們把js、css,img等靜態資源分離到另外一臺獨立域名的服務器上,在html頁面中再經過相應的標籤從不一樣域名下加載靜態資源,而被瀏覽器容許,基於此原理,咱們能夠經過動態建立script,再請求一個帶參網址實現跨域通訊。
1.)原生實現:
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參並指定回調執行函數爲onBack script.src = 'http://www.demo2.com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回調執行函數 function onBack(res) { alert(JSON.stringify(res)); } </script>
服務端返回以下(返回時即執行全局函數):
onBack({"status": true, "user": "admin"})
2.)jquery ajax:
$.ajax({ url: 'http://www.demo2.com:8080/login', type: 'get', dataType: 'jsonp', // 請求方式爲jsonp jsonpCallback: "onBack", // 自定義回調函數名 data: {} });
3.)vue.js:
this.$http.jsonp('http://www.demo2.com:8080/login', { params: {}, jsonp: 'onBack' }).then((res) => { console.log(res); })
後端node.js代碼示例:
var querystring = require('querystring'); var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { var params = qs.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回設置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
jsonp缺點:只能實現get一種請求。
2、 document.domain + iframe跨域
此方案僅限主域相同,子域不一樣的跨域應用場景。
實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。
1.)父窗口(http://www.demo.com/a.html)
<iframe id="iframe" src="http://child.demo.com/b.html"></iframe> <script> document.domain = 'demo.com'; var user = 'admin'; </script>
子窗口:(http://child.demo.com/b.html)
<script> document.domain = 'demo.com'; // 獲取父窗口中變量 alert('get js data from parent ---> ' + window.parent.user); </script>
3、 location.hash + iframe跨域
實現原理: a欲與b跨域相互通訊,經過中間頁c來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。
具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不一樣域只能經過hash值單向通訊,b與c也不一樣域也只能單向通訊,但c與a同域,因此c可經過parent.parent訪問a頁面全部對象。
實現原理: a欲與b跨域相互通訊,經過中間頁c來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。
具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不一樣域只能經過hash值單向通訊,b與c也不一樣域也只能單向通訊,但c與a同域,因此c可經過parent.parent訪問a頁面全部對象。
1.)a.html:(http://www.demo1.com/a.html)
<iframe id="iframe" src="http://www.demo2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); // 向b.html傳hash值 setTimeout(function() { iframe.src = iframe.src + '#user=admin'; }, 1000); // 開放給同域c.html的回調方法 function onCallback(res) { alert('data from c.html ---> ' + res); } </script>
2.)b.html:(http://www.demo2.com/b.html)
<iframe id="iframe" src="http://www.demo1.com/c.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); // 監聽a.html傳來的hash值,再傳給c.html window.onhashchange = function () { iframe.src = iframe.src + location.hash; }; </script>
3.)c.html:(http://www.demo1.com/c.html)
<script> // 監聽b.html傳來的hash值 window.onhashchange = function () { // 再經過操做同域a.html的js回調,將結果傳回 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); }; </script>
4、 window.name + iframe跨域
window.name屬性的獨特之處:name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。
1.)a.html:(http://www.demo1.com/a.html)
var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加載跨域頁面 iframe.src = url; // onload事件會觸發2次,第1次加載跨域頁,並留存數據於window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy頁)成功後,讀取同域window.name中數據 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域頁)成功後,切換到同域代理頁面 iframe.contentWindow.location = 'http://www.demo1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 獲取數據之後銷燬這個iframe,釋放內存;這也保證了安全(不被其餘域frame js訪問) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } }; // 請求跨域b頁面數據 proxy('http://www.demo2.com/b.html', function(data){ alert(data); });
2.)proxy.html:(http://www.demo1.com/proxy....
中間代理頁,與a.html同域,內容爲空便可。3.)b.html:(http://www.demo2.com/b.html)
<script> window.name = 'This is demo2 data!'; </script>
總結:經過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。
5、 postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是爲數很少能夠跨域操做的window屬性之一,它可用於解決如下方面的問題:
a.) 頁面和其打開的新窗口的數據傳遞
b.) 多窗口之間消息傳遞
c.) 頁面與嵌套的iframe消息傳遞
d.) 上面三個場景的跨域數據傳遞用法:postMessage(data,origin)方法接受兩個參數
data: html5規範支持任意基本類型或可複製的對象,但部分瀏覽器只支持字符串,因此傳參時最好用JSON.stringify()序列化。
origin: 協議+主機+端口號,也能夠設置爲"*",表示能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲"/"。1.)a.html:(http://www.demo1.com/a.html)
<iframe id="iframe" src="http://www.demo2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2傳送跨域數據 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.demo2.com'); }; // 接受domain2返回數據 window.addEventListener('message', function(e) { alert('data from demo2 ---> ' + e.data); }, false); </script>
2.)b.html:(http://www.demo2.com/b.html)
<script> // 接收domain1的數據 window.addEventListener('message', function(e) { alert('data from demo1 ---> ' + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 處理後再發回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.demo1.com'); } }, false); </script>
6、 跨域資源共享(CORS)
普通跨域請求:只服務端設置Access-Control-Allow-Origin便可,前端無須設置,若要帶cookie請求:先後端都須要設置。
需注意的是:因爲同源策略的限制,所讀取的cookie爲跨域請求接口所在域的cookie,而非當前頁。若是想實現當前頁cookie的寫入,可參考下文:7、nginx反向代理中設置proxy_cookie_domain 和 8、NodeJs中間件代理中cookieDomainRewrite參數的設置。
目前,全部瀏覽器都支持該功能(IE8+:IE8/9須要使用XDomainRequest對象來支持CORS)),CORS也已經成爲主流的跨域解決方案。
一、 前端設置:
1.)原生ajax
// 前端設置是否帶cookie xhr.withCredentials = true;
示例代碼:
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 // 前端設置是否帶cookie xhr.withCredentials = true; xhr.open('post', 'http://www.demo2.com:8080/login', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };
2.)jQuery ajax
$.ajax({ ... xhrFields: { withCredentials: true // 前端設置是否帶cookie }, crossDomain: true, // 會讓請求頭中包含跨域的額外信息,但不會含cookie ... });
3.)vue框架
在vue-resource封裝的ajax組件中加入如下代碼:Vue.http.options.credentials = true
二、 服務端設置:
若後端設置成功,前端瀏覽器控制檯則不會出現跨域報錯信息,反之,說明沒設成功。
1.)PHP後臺:
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 如有端口需寫全(協議+域名+端口) response.setHeader("Access-Control-Allow-Credentials", "true");
2.)Nodejs後臺示例:
ar http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var postData = ''; // 數據塊接收中 req.addListener('data', function(chunk) { postData += chunk; }); // 數據接收完畢 req.addListener('end', function() { postData = qs.parse(postData); // 跨域後臺設置 res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 後端容許發送Cookie 'Access-Control-Allow-Origin': 'http://www.demo1.com', // 容許訪問的域(協議+域名+端口) 'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly' // HttpOnly:腳本沒法讀取cookie }); res.write(JSON.stringify(postData)); res.end(); }); }); server.listen('8080'); console.log('Server is running at port 8080...');
7、 Nodejs中間件代理跨域
node中間件實現跨域代理,原理大體與nginx相同,都是經過啓一個代理服務器,實現數據的轉發,也能夠經過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登陸認證。
一、 非vue框架的跨域(2次跨域)
利用node + express + http-proxy-middleware搭建一個proxy服務器。
1.)前端代碼示例:
var xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問http-proxy-middleware代理服務器 xhr.open('get', 'http://www.demo1.com:3000/login?user=admin', true); xhr.send();
2.)中間件服務器:
var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/', proxy({ // 代理跨域目標接口 target: 'http://www.demo2.com:8080', changeOrigin: true, // 修改響應頭信息,實現跨域並容許帶cookie onProxyRes: function(proxyRes, req, res) { res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 修改響應信息中的cookie域名 cookieDomainRewrite: 'www.demo1.com' // 能夠爲false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...');
3.)Nodejs後臺
ar http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前臺寫cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly' // HttpOnly:腳本沒法讀取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
二、 vue框架的跨域(1次跨域)
利用node + webpack + webpack-dev-server代理接口跨域。在開發環境下,因爲vue渲染服務和接口代理服務都是webpack-dev-server同一個,因此頁面與代理接口之間再也不跨域,無須設置headers跨域信息了。
webpack.config.js部分配置:
module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.demo2.com:8080', // 代理跨域目標接口 changeOrigin: true, secure: false, // 當代理某些https服務報錯時用 cookieDomainRewrite: 'www.demo1.com' // 能夠爲false,表示不修改 }], noInfo: true } }
8、 WebSocket協議跨域
WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現。
原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。1.)前端代碼:
<div>user input:<input type="text"></div> <script src="./socket.io.js"></script> <script> var socket = io('http://www.demo2.com:8080'); // 鏈接成功處理 socket.on('connect', function() { // 監聽服務端消息 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 監聽服務端關閉 socket.on('disconnect', function() { console.log('Server socket has closed.'); }); }); document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value); }; </script>
2.)Nodejs socket後臺:
// 啓http服務 var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 監聽socket鏈接 socket.listen(server).on('connection', function(client) { // 接收信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 斷開處理 client.on('disconnect', function() { console.log('Client socket has closed.'); }); });
9、 nginx代理跨域
一、 nginx配置解決iconfont跨域
瀏覽器跨域訪問js、css、img等常規靜態資源被同源策略許可,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時可在nginx的靜態資源服務器中加入如下配置。
location / { add_header Access-Control-Allow-Origin *; }
二、 nginx反向代理接口跨域
跨域原理: 同源策略是瀏覽器的安全策略,不是HTTP協議的一部分。服務器端調用HTTP接口只是使用HTTP協議,不會執行JS腳本,不須要同源策略,也就不存在跨越問題。
實現思路:經過nginx配置一個代理服務器(域名與demo1相同,端口不一樣)作跳板機,反向代理訪問demo2接口,而且能夠順便修改cookie中demo信息,方便當前域cookie寫入,實現跨域登陸。
nginx具體配置:
#proxy服務器 server { listen 81; server_name www.demo1.com; location / { proxy_pass http://www.demo2.com:8080; #反向代理 proxy_cookie_demo www.demo2.com www.demo1.com; #修改cookie裏域名 index index.html index.htm; # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用 add_header Access-Control-Allow-Origin http://www.demo1.com; #當前端只跨域不帶cookie時,可爲* add_header Access-Control-Allow-Credentials true; } }
1.) 前端代碼示例:
var xhr = new XMLHttpRequest(); // 前端開關:瀏覽器是否讀寫cookie xhr.withCredentials = true; // 訪問nginx中的代理服務器 xhr.open('get', 'http://www.demo1.com:81/?user=admin', true); xhr.send();
2.) Nodejs後臺示例:
var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前臺寫cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.demo2.com;HttpOnly' // HttpOnly:腳本沒法讀取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');