最近在做一個音樂webapp的時候,遇到這樣一個需求:提取歌曲圖片的主題色,然後應用到全域性。
一開始的思路是把圖片繪入到canvas中利用getImageData()獲取圖片的畫素資料,分析這些資料得出最接近圖片的顏色。接著問題來了,如果在canvas繪入跨域資源,canvas將受到汙染,無法呼叫方法(因為資料都是在QQ音樂官網抓取的)。說到這,我們就說說前端跨域的那些事。
對專案感興趣的點這裡?專案地址
什麼是跨域
一般來說,當一個請求url的協議、域名、埠三者之間任意一個與當前頁面地址不同即為跨域。最常見的就是在一個域名下的網頁中,呼叫另一個域名中的資源。
當瀏覽器報這樣的錯的時候,就是跨域請求出問題了
為啥要跨域
主要是為了安全,瀏覽器採用同源策略,對js進行限制,防止惡意使用者獲取非法資料,同時還防止了大部分XSS攻擊(就是向使用者介面注入js指令碼)。
瀏覽器的兩種同源策略會造成跨域問題:
- DOM同源策略。禁止對不同源的頁面的DOM進行操作,主要包括iframe、canvas之類的。不同源的iframe禁止資料互動的,含有不同源資料的canvas會受到汙染而無法進行操作。
- XmlHttpRequest同源策略。簡單來說就禁止不同源的AJAX請求,主要用來防止CSRF攻擊。
跨域的時候瀏覽器為啥會報錯
這是因為W3C推出的了一個標準----"跨域資源共享"(Cross-origin resource sharing),簡稱CORS。該標準定義了跨域訪問資源時伺服器和瀏覽器怎麼通訊。通俗講就是瀏覽器在發現跨域請求的時候會附加一些頭資訊和伺服器進行溝通,來確定跨域請求通不通過。現在除IE10以下的瀏覽器都支援這個標準。
瀏覽器會把跨域請求分成兩類:簡單和非簡單請求。
簡單請求
簡單請求有以下特徵:
- 請求方法是以下之一
- GET
- POST
- HEAD
- 頭資訊是以下欄位之一
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type //該欄位型別不能是application/json
同時滿足以上兩點的就是簡單請求,其他就是非簡單請求了。
當瀏覽器把跨域請求識別為簡單請求的時候,就會在頭資訊裡附加上一個Origin欄位,該欄位會把這次請求的來源(協議、域名、埠)帶給伺服器,伺服器就會檢查這個請求的來源。
要是伺服器同意了這個來源呢,在正常回復瀏覽器的同時,就也附加上幾條欄位作為回禮:
- Access-Control-Allow-Origin // 這條寫著服務同意的來源,或者一個代表所有來源的 “*”
- Access-Control-Allow-Credentials // 這條寫著瀏覽器可以發Cookie過來了
總的來說得到伺服器的認可了,這樣瀏覽器就能正常收到迴應了
要是不同意,伺服器就正常返回資料,啥也不附加,瀏覽器見不到Access-Control-Allow-Origin會不高興的,然後就不給你返回的資料了,再然後就是報錯,這個錯就是上面那這樣的(就是提取顏主題色的時候?)。 而且狀態碼還是各種各樣的,甚至有可能是200?
非簡單請求
這種不簡單的請求,比如PUT或DELETE請求,還有 Content-Type欄位型別為
application/json的。瀏覽器會嚴格一點,在發跨域請求前,會發個“預檢”請求看看伺服器的態度先,這個預檢請求比較特殊,請求方式叫OPTIONS,頭資訊裡不光有Origin欄位還有這倆:
- Access-Control-Request-Method // 這條是告訴伺服器等會的跨域請求是啥方式
- Access-Control-Request-Headers // 這條是瀏覽器跨域請求的時候要額外附加的資訊
伺服器收到預檢請求提交過來的資訊後,也會嚴格一點,不僅檢查來源,還檢查請求方式和頭資訊欄位。
要是伺服器同意了,就在正常的HTTP迴應中附加上Access-Control-Allow-Origin欄位,也同樣寫著服務同意的來源。
這就代表這拿到伺服器的認可了,畢竟是經歷過嚴格檢查的,接下來的每次跨域請求都會正常進行。
要是不同意,伺服器也是啥都不附加地正常回應,這個時候瀏覽器看不見Access-Control-Allow-Origin可是會生氣的,連跨域請求都懶得發,直接報錯。(這種情況還沒碰到,就不上圖啦)。
所以說報錯是瀏覽器搞得鬼。
如何解決跨域問題
日常開發中會經常碰到跨域的問題,我們來看看常見的解決方法:
1.JSONP
像img、script等標籤是沒有跨域限制的,於是乎程式猿們就想到一個辦法,動態建立script標籤,通過src屬性來進行跨域請求的來源
function fun(data) {
console.log(data);
};
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'http://example.com?jsonp=cb';
body.appendChild(script);複製程式碼
返回的js指令碼會直接執行,這樣我們想要的資料就傳了進來。
這種方法所有瀏覽器都相容,前端可以很輕鬆的做到跨域請求,但也有一些缺點:
- 只能通過GET方式請求,一方面是引數長度有限制,二是安全性比較差;
- 後端需要知道前端的cb是什麼樣的結構,主要在引數和回撥名;
- 後端需要進行引數和cb的拼接然後才能執行;
本文的需求是要把圖片繪入到canvas裡,這個方法就行不通了,看下一種
2.伺服器代理
伺服器不像瀏覽器那樣有跨域限制,可以讓伺服器去請求跨域資源然後再返回給客戶端,就拿canvas操作跨域圖片來說,客戶端把跨域的url傳給伺服器,請求到圖片後再傳回客戶端,就可以解決開頭說到的那個問題了(上程式碼啦~)
圖片是以二進位制流的方式在http協議中傳輸,所以一定要注意編碼格式,否則就返回一堆不知道是啥的東西啦
首先在後臺起一個express服務,這裡的get請求用的是npm中的https包
apiRoutes.get('/image', function (req, res) {
const Url = (req.query)['0'];
https.get(Url, function (response) {
response.setEncoding('binary'); //二進位制binary
var type = response.headers["content-type"];
let Data = '';
response.on('data', function (data) { //載入到記憶體
Data += data;
}).on('end', function () { //載入完
res.writeHead(200, { 'Access-Control-Allow-Origin': '*', "Content-Type": type }); //設定頭,允許跨域
res.end(new Buffer(Data, 'binary'));
})
})
});
app.use('/api', apiRoutes)複製程式碼
然後把跨域的圖片url提交到這個路由上,就可以‘偽裝’成同源圖片啦
http://example.com/api/image?0=(跨域的圖片url)複製程式碼
—————————————— 更新 11.17 ————————————————
關於主題色的提取在這裡?主題色提取
以上便是這次的文章分享了,歡迎留言相互學習~