【Web技術】424- 那些年曾談起的跨域
- 2019 年 11 月 30 日
- 筆記
正文從這裡開始~
對於前端開發來說跨域應該是最不陌生的問題了,無論是開發過程中還是在面試過程中都是一個經常遇到的一個問題,在開發過程中遇到這個問題的話一般都是找後端同學去解決,以至於很多人都忽略了對跨域的認識。為什麼會導致跨域?遇到跨域又怎麼去解決呢?本文會對這些問題一一的介紹。
在JavaScript
中,在不同的域名下面進行數據交互,就會遇到跨域問題,說到跨域首先要從同源說起,瀏覽器為了提供一種安全的運行環境,各個瀏覽器廠商協定使用同源策略。
什麼同源策略
同源策略:同源策略(Same origin policy
)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說 Web 是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。
同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS
、CSRF
等攻擊。所謂同源是指協議+域名+埠
三者相同,即便兩個不同的域名指向同一個ip
地址,也非同源。
Url 組成部分
了解同源策略以後,同樣需要對url
的組成部分也順帶了解一下吧,只有了解url
之後當出現跨域的時候才知道哪裡出了問題,這樣才能和後端、運維開懟,懟天懟地對空氣。O(∩_∩)O 哈哈~ 其實不是啦,當然是為了能夠和其他同事能做到良好的溝通,說的的有理有據,以理服人嘛,這才是王道。
從上圖中能夠清晰的看出url
中每個部分的含義:
- protocol:
協議
常用的協議是http
- auth:
驗證
,因為明文傳輸用戶名和密碼,非HTTPS
環境下很不安全,一般用的非常少。 - hostname:
主機地址
,可以是域名,也可以是 IP 地址 - port:
埠
http 協議默認埠是:80 埠,如果不寫默認就是: 80 埠 - pathname:
路徑
網路資源在伺服器中的指定路徑 - serarch:
查詢字元串
如果需要從伺服器那裡查詢內容,在這裡編輯 - hash:
哈希
網頁中可能會分為不同的片段,如果想訪問網頁後直接到達指定位置,可以在這部分設置
項目過程中經常會用到一些快取,瀏覽器為了網頁的安全在快取的時候,由於同源策略的問題對其快取內容進行了限制,其實想想也是對的,如果不使用同源策略的話,很容易衝掉快取的東西。
Cookie
、LocalStorage
和IndexDB
等無法讀取。DOM
無法獲得。AJAX
請求不能發送。
當然瀏覽器也沒有把所有的東西都限制了,比如圖片、互聯網資源等還是允許跨域請求的。允許跨域請求都是使用標籤,只有三個標籤是允許跨域載入資源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
在項目開發過程中時不時的就會遇到下面這樣拋出了錯誤,有的人可能在開發過程中沒有遇到過,如果是的話,你可能遇到一個很不錯的後端或者運維。
XMLHttpRequest cannot loadhttp://www.******.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
上面的報錯就是典型的跨域報錯,既然跨域這麼常見到底都有哪些情況會導致跨域的問題:
說明 |
是否允許通訊 |
---|---|
同一域名下 |
允許 |
同一域名下不同文件夾 |
允許 |
同一域名,不同埠 |
不允許 |
同一域名,不同協議 |
不允許 |
域名和域名對應 ip |
不允許 |
主域名相同,子域名不同 |
不允許 |
同一域名,不同二級域名 |
不允許 |
不同域名 |
不允許 |
跨域解決方案
由於瀏覽器的限制造成了很多的跨域問題,同樣也是為了安全,既然出現了跨域就必定要有一些對應的解決方案,總不能遇到跨域之後項目就不做了啊,可能瞬間就涼了。閑話就不多扯了。
JSONP
在遇到跨域的時候經常會提及到的一個詞就是JSONP
,一直在說JSONP
?可是通過什麼原理來實現的呢?我覺得應該了解一下到底什麼再去了解一下實現固然原理也就懂得咯。
什麼是 JSONP
JSONP:JSON
的一種 「使用模式」,可用於解決主流瀏覽器的跨域數據訪問的問題。由於同源策略,一般來說位於server1.example.com
的網頁無法與不是server1.example.com
的伺服器溝通,而HTML
的<script>
元素是一個例外。利用<script>
元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON
資料,而這種使用模式就是所謂的JSONP
。用JSONP
抓到的資料並不是JSON
,而是任意的JavaScript
,用JavaScript
直譯器執行而不是用JSON
解析器解析。- 選自百度百科
對於JSONP
簡單的百度了一下,百度給出的解釋如上,看完整段話,有一些小的收穫,第一script
標籤具有開放策略,可以使用src
的開放性解決其跨域問題。在這裡簡單的闡述一下個人觀點。JSONP
可以分為兩個部分來解讀,JSON
與padding
,JSON
固然就不用解釋了,只是一種數據格式,padding
在css
中是內填充的意思,其實JSONP
的原理與內填充有些類似。通過把數據填充js
文件中然後引入到頁面中,並在頁面中使用。
有沒有注意過百度,其實百度的即時搜索就是使用JSONP
來實現的,可以嘗試一下,在百度中搜索一下,就會在Network
中看到一個以sugrec
為開頭的請求,這個請求就是使用的JSONP
的形式,為了大家方便特意截選了一個段連接。
連接: https://www.baidu.com/sugrec?prod=pc&wd=json&pwd=json&cb=query 返回格式: query({ "q": "json", "p": false, "g": [{ "type": "sug", "sa": "s_1", "q": "json格式" }, { "type": "sug", "sa": "s_2", "q": "jsonp" }, { "type": "sug", "sa": "s_3", "q": "json解析" }, ...] })
通過對百度的即時搜索的分析就可以簡單的看出JSONP
的實現原理,請求會的js
文件中包含一個函數,其函數名稱就是連接中cb
的參數最為參數傳給後台,後台通過處理並在執行這個與參數對應的函數的,當函數執行的時候將把數據以實參的形式傳遞給對應的函數,解決跨域問題。為了方便閱讀這裡只截取了程式碼片段。
案例:
前端程式碼:
$('#btn').click(function(){ var frame = document.createElement('script'); frame.src = 'http://localhost:5000/jsonp?name=aaron&age=18&callback=query'; $('body').append(frame); }); function query(res){ console.log(res.message+res.name+'你已經'+res.age+'歲了'); }
後端程式碼:
router.get('/jsonp', (req, res) => { let {name,age,callback} = req.query; let data = {message:'success',name,age}; data = JSON.stringify(data); res.end(`${callback}(${data})`); });
通過如上程式碼就可以簡單的實現JSONP
,雖然JSONP
解決了跨域的問題,還是有很多弊端的,比如會在頁面中添加一些script
標籤,數據不能雙向操作等等。
使用JSONP
的時候尤其要注意一點,一定要把插入的script
放到所應用函數的下面。這個和 js 的執行順序有關係,如果把script
標籤放在上面的話,其方法還沒有被讀取在script
標籤中就執行了這個方法必定報錯的,這點很重要哦。
document.domain
document.domain
項目中一般應用的較少,默認情況下document.domain
存放的是載入文檔的伺服器的主機名。可以在控制台輸出一下,得到的則是segmentfault.com
這個域名。我在項目中所用到的則是結合iframe
的時候遇到的跨域,並使用的domain
解決的。
在使用document.domain
實現跨域的時候需要注意一下,這兩個域名必須屬於同一個一級域名! 而且所用的協議,埠都要一致,否則無法利用document.domain
進行跨域。Javascript
出於對安全性的考慮,而禁止兩個或者多個不同域的頁面進行互相操作。而相同域的頁面在相互操作的時候不會有任何問題。
簡單的解釋一下,例如想要在www.a.com
中將看到segmentfault.com
中的內容並將其網頁使用iframe
將其嵌入到其網頁中,但是此時瀏覽器是不允許通過JavaScript
直接操作segmentfault.com
的,因為這兩個頁面屬於不同的域,在操作之前瀏覽器會檢測是否符合約源策略,如果符合則允許操作,反之則不行。
若想要同過document.domain
實現跨域的話,必須使其滿足同源策略,這個時候就需要用到document.domain
,document.domain
都設成相同的域名就可以了。但要注意的是,document.domain
的設置是有限制的,我們只能把document.domain
設置成自身或更高一級的父域,且主域必須相同。
例如:
a.com news.a.com
news.a.com
屬於a.com
的一個子域名,按照上面所說已經滿足了上面的規則,如果想要實現跨域操作就需要對接子頁面的document.domain
進行操作。
父頁面:
document.domain = 'a.com'; var ifr = document.createElement('iframe'); ifr.src = 'news.a.com/map.html'; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; var oUl = doc.getElementById('ul1'); alert(oUl.innerHTML); ifr.onload = null; };
子頁面:
document.domain = 'a.com'; $ajax.get({ // ...省略 })
其實現原理就是通過iframe
載入一個與你想要通過ajax
獲取數據的目標頁面處在相同的域的頁面,所以這個iframe
中的頁面是可以正常使用ajax
去獲取你要的數據的,然後就是通過我們剛剛講得修改document.domain
的方法,讓我們能通過 js 完全控制這個iframe
,這樣我們就可以讓iframe
去發送ajax
請求,然後收到的數據我們也可以獲得了。
location.hash
若理解了document.domain
實現跨域原理,那麼location.hash
也就很好理解了,其原理與document.domain
很相似一樣都是動態插入一個iframe
,然後把iframe
的src
指向服務端地址,而服務端同樣都是輸出一段JavaScript
程式碼,同樣都是利用和子窗口之間的通訊完成數據傳輸,同樣要針對同源策略做出處理。
既然說到了hash
到底什麼是hash
這裡也就單獨的說一下吧,雖然很好理解,但是對於新同學來說可能還不知道hash
具體是什麼?
hash: 一般翻譯做散列、雜湊,或音譯為哈希,是把任意長度的輸入(又叫做預映射 pre-image)通過散列演算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。– 節選自百度百科
讀完之後感覺自己整個人都不好了,有些似懂非懂的意思,我所理解的哈希是指一個過程,這個過程就是把任意長度的輸入,通過哈希演算法,變換成固定長度的輸出,所輸出的稱為哈希值。這種變換是一種壓縮映射,也即哈希值所佔的空間一般來說遠小於輸入值的空間,不同的輸入可能會哈希出相同的輸出(概率很小)。
哈希有如下特點:
- 如果兩個哈希值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入一定是不相同的。
- 如果兩個哈希值相同,兩個輸入值很可能 (極大概率) 是相同的,但也可能不同,這種情況稱為「哈希碰撞」
- 抗篡改能力:對於一個數據塊,哪怕只改動其一個比特位,其 hash 值的改動也會非常大。
- 它是一種單向函數是 「非對稱」 的,即它是一個從明文到密文的不可逆的映射, 只有加密過程, 沒有解密過程。
那麼哈希在平時項目開發中有什麼用途呢?可以用哈希來做什麼事情?對於前端來說用到哈希最多的時候可能就是錨點定位。通過不同的哈希值定位到描點指定的元素位置上。
<a href='#1'>red</a> <a href='#2'>black</a> <a href='#3'>yellow</a> <a href='#4'>pink</a> <div id='1' style='width:500px;height:200px;background-color:red'> </div> <div id='2' style='width:500px;height:200px;background-color:black'> </div> <div id='3' style='width:500px;height:200px;background-color:yellow'> </div> <div id='4' style='width:500px;height:1200px;background-color:pink'> </div>
關於更多細節的東西不再這裡贅述了,如果想要了解更多的話大家可以自行google
,再說下去的話可能就跑題了。
簡單的介紹了一下哈希與哈希的用處那麼又該如何使用哈希來實現跨域呢?其實很簡單,如果index
頁面要獲取遠端伺服器的數據,動態插入一個iframe
,將iframe
的 src 屬性指向服務端地址。這時top window
和包裹這個iframe
的子窗口由於同源策略的原因是不能直接通訊的,所以改變子窗口的路徑就行了,將數據當做改變後的路徑的hash
值加在路徑上,然後就能通訊了,將數據加在index
頁面地址的hash
值上。index
頁面監聽地址的hash
值變化然後做出判斷,處理數據。
父頁面:
<iframe src="http://localhost:7000/b.html#key=1&key1=2"></iframe>
由於哈希值的改變不會改變網頁的網址的,所以服務端可以通過獲取到哈希來解析url
中的參數,並把數據返回給前端即可。通過parent.location.hash
去改變哈希值,然後就可以像document.domain
一樣去獲取到子頁面的數據了。parent.location.hash
該方法是有局限性的,在IE
和Chrome
中是不支援這種操作的。那麼整個問題應如何解決呢?
在同域的域名下面添加一個*.html
(* 代表任意名) 文件,然後把通過iframe
把*.html
引入到父頁面中,並把需要請求的介面iframe
添加到*.html
中去請求,這樣就可以解決了。
http://localhost:6000/a.html
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> <title>無</title> </head> <body> <script type="text/javascript"> function sendRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://localhost:7000/b.html#Aaron'; document.body.appendChild(ifr); } function checkHash(){ var data = location.hash?location.hash.substring(1):''; if(data) location.hash = ''; } setInterval(checkHash,1000); window.onload = sendRequest; </script> </body> </html>
http://localhost:7000/b.html
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> <title>無</title> </head> <body> <script type="text/javascript"> function checkHash(){ var data = ''; switch(location.hash){ case '#Aaron': data = 'my Aaron'; break; case '#Angie': data = 'my Angie'; break; default : break; } data && callBack('#'+data); } function callBack(hash){ var proxy = document.createElement('iframe'); proxy.style.display = 'none'; proxy.src = 'http://localhost/c.html'+hash; document.body.appendChild(proxy); } window.onload = checkHash; </script> </body> </html>
http://localhost:6000/c.html
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> <title>無</title> </head> <body> <script type="text/javascript"> parent.parent.location.hash = self.location.hash.substring(1); </script> </body> </html>
a.html
中有一個隱藏的iframe
, 該iframe
指向異域http://localhost:7000/b.html
的b.html
,且傳遞hash
值給b.html``b.html
獲取hash
值,生成data
值,然後動態創建iframe
,該iframe
將data
值傳給與a.html
同域的c.html
因為c.html
與a.html
同域,可以傳值固然也就解決了跨域問題。
window.name
window.name
這個屬性不是一個簡單的全局屬性只要在一個window
下,無論url
怎麼變化,只要設置好了window.name
,那麼後續就一直都不會改變,同理,在iframe
中,即使 url 在變化,iframe
中的window.name
也是一個固定的值,利用這個,我們就可以實現跨域了。
http://localhost:6000/a.html
<iframe src="http://localhost:7000/b.html" frameborder="1"></iframe> <script> var ifr = document.querySelector('iframe') ifr.style.display = 'none' var flag = 0; ifr.onload = function () { if (flag == 1) { ifr.contentWindow.close(); } else if (flag == 0) { flag = 1; ifr.contentWindow.location = 'http://localhost:6000/proxy.html'; } } </script>
http://localhost:7000/b.html
var person = { name: 'Aaron', age: 18 } window.name = JSON.stringify(person)
http://localhost:6000/proxy.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>proxy</title> </head> <body> <p>這是proxy頁面</p> </body> </html>
在http://localhost:6000
下有一個a.html
,在http://localhost:7000
下有一個b.html
,在http://localhost:6000/a.html
中創建了一個iframe
標籤並把地址指向了http://localhost:7000/b.html
,在b.html
中的window.name
賦值保存了一段數據,但是現在還獲取不了,因為是跨域的,所以,我們可以把src
設置為當前域的http://localhost:6000/proxy.html
,雖然域名改變了但是window.name
是沒有改變的。這樣就可以拿到我們想要的數據了。
postMessage(HTML5)
可能很多不知道postMessage
整個API
, 在HTML5
中新增了postMessage
方法允許來自不同源的腳本採用非同步方式進行有限的通訊,可以實現跨文本檔、多窗口、跨域消息傳遞,postMessage
在很多瀏覽器中都已經得到了良好的支援,所以可放心的使用。該方法可以通過綁定window
的message
事件來監聽發送跨文檔消息傳輸內容。
postMessage()
方法接受兩個參數
- 1. data: 要傳遞的數據,html5 規範中提到該參數可以是
JavaScript
的任意基本類型或可複製的對象,然而並不是所有瀏覽器都做到了這點兒,部分瀏覽器只能處理字元串參數,所以我們在傳遞參數的時候需要使用JSON.stringify()
方法對對象參數序列化,在低版本IE
中引用json2.js
可以實現類似效果。
- origin:字元串參數,指明目標窗口的源,
協議+主機+埠號+URL
,URL
會被忽略,所以可以不寫,這個參數是為了安全考慮,postMessage()
方法只會將message
傳遞給指定窗口,當然如果願意也可以建參數設置為 "*",這樣可以傳遞給任意窗口,如果要指定和當前窗口同源的話設置為 "/"。
http://localhost:6000/a.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>無</title> </head> <body> <div> <input id="text" type="text" value="My name』s Aaron" /> <button id="send" >發送消息</button> </div> <iframe id="receiver" src="http://localhost:7000/b.html"></iframe> <script> window.onload = function() { var receiver = document.getElementById('receiver').contentWindow; var btn = document.getElementById('send'); btn.addEventListener('click', function (e) { e.preventDefault(); var val = document.getElementById('text').value; receiver.postMessage("Hello "+val+"!", "http://localhost:7000"); }); } </script> </body> </html>
http://localhost:7000/b.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>無</title> </head> <body> <div id="message"> Hello World! </div> <script> window.onload = function() { var messageEle = document.getElementById('message'); window.addEventListener('message', function (e) { if (e.origin !== "http://localhost:6000") { return; } messageEle.innerHTML = "從"+ e.origin +"收到消息: " + e.data; }); } </script> </body> </html>
這樣我們就可以接收任何窗口傳遞來的消息了,為了安全起見,我們利用這時候的MessageEvent
對象判斷了一下消息源,MessageEvent
對象,這個對象中包含很多東西。
- data:顧名思義,是傳遞來的
message
- source:發送消息的窗口對象
- origin:發送消息窗口的源(協議 + 主機 + 埠號)
使用postMessage
方法比以上方法用起來要輕便,不必有太多的繁瑣操作,可以說postMessage
是對於解決跨域來說是一個比較好的解決方案,不會顯得程式碼特別的臃腫,並且各個瀏覽器又有良好的支援。
跨域資源共享 (CORS)
CORS:全稱 "跨域資源共享"(Cross-origin resource sharing
)。CORS
需要瀏覽器和伺服器同時支援,才可以實現跨域請求,目前幾乎所有瀏覽器都支援CORS
,IE
則不能低於IE10
。CORS
的整個過程都由瀏覽器自動完成,前端無需做任何設置,跟平時發送ajax
請求並無差異。CORS
的關鍵在於伺服器,只要伺服器實現CORS
介面,就可以實現跨域通訊。
跨域資源共享 (CORS
) 是一種機制,它使用額外的HTTP
頭來告訴瀏覽器讓運行在一個origin (domain)
上的Web
應用被准許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域HTTP
請求。在上面說過src
是不受同源策略限制的,但是出於安全原因,瀏覽器限制從腳本內發起的跨源HTTP
請求。例如,XMLHttpRequest
和FetchAPI
遵循同源策略。這意味著使用這些API
的Web
應用程式只能從載入應用程式的同一個域請求HTTP
資源,除非響應報文包含了正確CORS
響應頭。
所有CORS
相關的的頭都是Access-Control
為前綴的。下面是每個頭的一些細節。
欄位 |
描述 |
---|---|
Access-Control-Allow-Methods |
該欄位必需,它的值是逗號分隔的一個字元串,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次 "預檢" 請求 |
Access-Control-Allow-Headers |
如果瀏覽器請求包括 Access-Control-Request-Headers 欄位,則 Access-Control-Allow-Headers 欄位是必需的。它也是一個逗號分隔的字元串,表明伺服器支援的所有頭資訊欄位,不限於瀏覽器在 "預檢" 中請求的欄位 |
Access-Control-Allow-Credentials |
該欄位與簡單請求時的含義相同。 |
Access-Control-Max-Age |
該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是 20 天(1728000 秒),即允許快取該條回應 1728000 秒(即 20 天),在此期間,不用發出另一條預檢請求。 |
import express from "express"; import cors from "cors"; const app = express(); const corsOptions = { origin: 'http://example.com', optionsSuccessStatus: 200 } app.get('/products/:id', cors(corsOptions), (req, res, next) => { res.json({msg: 'This is CORS-enabled for only example.com.'}) }) app.listen(80, function () { console.log('啟用corba,埠:80') })
使用CORS
簡單請求,非常容易,對於前端來說無需做任何配置,與發送普通ajax
請求無異。唯一需要注意的是,需要攜帶cookie
資訊時,需要將withCredentials
設置為true
即可。CORS
的配置,完全在後端設置,配置起來也比較容易,目前對於大部分瀏覽器兼容性也比較好,現在應用最多的就是CORS
解決跨域了。
在開發過程中由於各個公司的差異選用的介面風格也是不同的,很多公司會採用 RESTful 風格去編寫介面,難免就會有些騷操作,在跨域請求時自定義的請求頭是不允許這樣操作的,因為瀏覽器根據Response Headers
判斷請求是否允許。
跨域時默認允許的方法
- GET
- HEAD
- POST
因為瀏覽器希望在網頁進行跨域請求操作的時候是保證服務端的安全的,不允許任何隨便進行跨域,不允許隨便的方法進行跨域,以防數據被惡意篡改。所以提供這些限制之後,就可以進行一些非常有利的判斷。針對如上問題需要服務端進行特殊處理才行
const http = request("http"); http.createServer(function(request,response){ response.writeHead(200,{ // 設置允許跨域的訪問地址 'Access-Control-Allow-Origin':'*', // 設置允許訪問的自定義請求頭 'Access-Control-Allow-Headers':'X-Test-Cors', // 設置允許跨域的methods 'Access-Control-Allow-Methods':'POST,Put,Delete', // 允許以上三個方式進行跨域的最長時間,1000秒內不需要發送預請求驗證了 'Access-Control-Max-Age':'1000' }) response.end('123') }).listen(3000)
這樣設置,這個請求就成功了,但是可以觀測到多了一個請求,這個多出來的請求就是預請求,告訴瀏覽器,這個自定義請求是允許的,然後再正式的發送這個請求,通過驗證之後就請求成功了,瀏覽器為什麼要做這些限制,是因為保證安全,不允許隨便一個人都可以進行跨域。
WebSocket 協議跨域
WebSocket
是HTML5
新推出的一個API
,通過WebSocket
可以實現客戶端與服務端的即時通訊,如聊天室,服務數據同步渲染等等。WebSocket
是點對點通訊,服務端與客戶端可以通過某種標識完成的。WebSocket
是不受同源策略限制的所以可以利用這個特性直接與服務端進行點對點通訊。
以下示例沒有使用HTML5
的WebSocket
而是使用的socket.io
完成類似的功能操作。
若若的說一句:其實我一直以為WebSocket
與Ajax
一樣是受同源策略
限制的,經過學習才發現不是的。真是學到老活到老 (關谷口音)。O(∩_∩)O
服務端:
var io = require('socket.io')(1234); io.sockets.on('connection', (client) => { client.on('message', function (msg) { //監聽到資訊處理 client.send('伺服器收到了資訊:' + msg); }); client.on("disconnect", function () { //斷開處理 console.log("client has disconnected"); }); }); console.log("listen 1234...");
客戶端:
$(function () { var iosocket = io.connect('http://localhost:1234/'); var $ul = $("ul"); var $input = $("input"); iosocket.on('connect', function () { //接通處理 $ul.append($('<li>連上啦</li>')); iosocket.on('message', function (message) { //收到資訊處理 $ul.append($('<li></li>').text(message)); }); iosocket.on('disconnect', function () { //斷開處理 $ul.append('<li>Disconnected</li>'); }); }); $input.keypress(function (event) { if (event.which == 13) { //回車 event.preventDefault(); console.log("send : " + $input.val()); iosocket.send($input.val()); $input.val(''); } }); });
Websocket
既然能支援跨域方法,那就是說,一個開放給公網的Websocket
服務任何人都能訪問,這樣的話會使數據變得很不安全,所以可以通過對連接域名進行認證即可。
伺服器反代
學習路程首先了解了一下什麼是反代,在電腦網路中,反向代理是代理伺服器的一種。伺服器根據客戶端的請求,從其關聯的一組或多組後端伺服器(如 Web 伺服器)上獲取資源,然後再將這些資源返回給客戶端,客戶端只會得知反向代理的IP
地址,而不知道在代理伺服器後面的伺服器簇的存在。– 節選自百度百科
反向代理伺服器:就nginx
把http
請求轉發到另一個或者一些伺服器上。從而輕鬆實現跨域訪問。比如伺服器中分別部署了 N 個伺服器,當客戶端發起請求時不用直接請求伺服器中N
個節點上的服務,只需要訪問我們的代理伺服器就行了,代理伺服器根據請求內容分發到不同伺服器節點。這僅是一種使用場景,當然還可以做負載均衡等。
反向代理理解起來不是特別的難,平時生活中最常見的例子,當我們撥打人工客服的時候,並不是直接撥打客服的某一個電話號碼,而是撥打總機號碼,當我們撥打然後由總機進行處理,然後再分發給不同的客服人員。r 然而當服務人員需要讓你掛斷電話等待回撥的時候,也不是直接撥打到你的電話,同樣是也通過總機之後再轉發到你的電話。其實這個總機也就相當於反代伺服器。雖然這個例子不太貼切但是多多少少就是這個意思。
由於不太懂Nginx
不知道該如何處理這個部分,只是對反向代理做了一個簡單的了解,等以後學習了Nginx
會補上相關程式碼。
Nodejs 代理跨域
使用Nodejs
進行跨域在我看來,就是使用Node
服務做了一個中間代理轉發,其原理和反向代理差不多,當訪問某一個URL
時需要通過伺服器分發到另一個伺服器URL
地址中。這裡就不過多的贅述了,直接看程式碼吧。
示例程式碼入下:
main.js
import http from "http"; import httpProxy from "http-proxy"; // 新建一個代理 Proxy Server 對象 const proxy = httpProxy.createProxyServer({}); // 捕獲異常 proxy.on('error', function (err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('error'); }); // 在每次請求中,調用 proxy.web(req, res config) 方法進行請求分發 const server = http.createServer((req, res) => { // 在這裡可以自定義你的路由分發 let host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; switch(host){ case 'www.a.com': proxy.web(req, res, { target: 'http://localhost:3000' }); break; case 'www.b.com': proxy.web(req, res, { target: 'http://localhost:4000' }); break; default: res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello Aaron!'); } }); server.listen(8080);
如程式碼所示,當訪問www.a.com
的時候,請求就被轉發到了3000
介面上,訪問www.b.com
時就被轉發到了4000
這個介面上。這樣就簡單的完成了一個反向代理的工作。
在使用vue
開發的時候難免也會遇到跨域問題,或許你根本就沒有遇到,可能你們正好處於同一個域裡面,還有一種可能就是,後端同學或者運維同學已經處理好有關跨域的相關操作。但是當在開發過程中遇到跨域的時候,什麼前端應該有對應的解決辦法。vue-cli
是基於Node
服務的,所以我們可以利用這個服務來做代理工作,暫時解決開發中的跨域問題。
build/webpack.config.js
module.exports = { devServer: { historyApiFallback: true, proxy: [{ context: '/login', // url以/login為開頭時啟用代理 target: 'http://www.a.com:8080', // 代理跨域目標介面 changeOrigin: true, secure: false, // 當代理某些https服務報錯時用 cookieDomainRewrite: 'www.b.com' // 可以為false,表示不修改 }], noInfo: true } }
在開發過程中遇到的可以通過這種方式解決,但是到達生產環境時到底使用什麼方法還是需要斟酌的,畢竟要使服務數據變得更加的安全才是最好的。
總結
以上講了很多有關跨域的解決方案,有利也有弊,對於我而言可能更加的傾向於後端粑粑或者運維粑粑去解決跨域問題,畢竟前端處理起來畢竟不是很安全,而且後端或者運維處理起來也不是那麼的麻煩。
很感謝大家利用這麼長時間來讀這篇文章,文章中若有錯誤請在下方留言,會儘快做出修改。
原創系列推薦
▼
5. Webpack4 入門(上)|| Webpack4 入門(下)
6. MobX 入門(上) || MobX 入門(下)
7. 59篇原創系列匯總