在製做oneday-music-player的時候要使用ajax向百度音樂的api發送請求,而後出現了
XMLHttpRequest cannot load 'http://....' . No 'Access-Control-Allow-Origin' header is present on the request resource. Origin 'http://....' is therefore not allowed access
,通過搜索發現是受到了同源策略的影響而致使的跨域問題,因此學習一下關於跨域的知識點。javascript
同源策略限制從一個源加載的文檔或腳本與另外一個源的文檔或腳本進行交互的方式,是隔離潛在惡意文件的重要安全機制。html
兩個頁面擁有一樣的協議、端口(若是指定)和域名時,能夠說兩個頁面是同源的。html5
下表是相對於http://store.company.com/dir/page.html
同源檢測的示例:java
url | 結果 | 緣由 |
---|---|---|
http://store.company.com/dir2/other.html |
成功 | |
http://store.company.com/dir/inner/other.html |
成功 | |
https://store.company.com/secure.html |
失敗 | 不一樣協議(https 和http ) |
http://store.company.com:81/dir/etc.html |
失敗 | 不一樣端口(81和80) |
http://news.company.com/dir/other.html |
失敗 | 不一樣域名(news 和store ) |
而若是非同源,則有三種行爲會受到限制:git
Cookie是服務器寫入瀏覽器的一小段信息,只有同院的網頁才能共享。可是,兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過document.domain
共享Cookiegithub
例如,假設文檔中的一個腳本在http://store.company.com/dir/page.html
執行如下語句:web
document.domain = "company.com"
此時,http://news.company.com/dir/other.html
和http://store.company.com/dir/other.html
就能夠經過document.cookie
來設置或獲取Cookie,即共享Cookie。ajax
可是這種方法適用於Cookie和iframe窗口,LocalStorage和IndexDB沒法經過這種方法規避同源策略。json
若是兩個網頁不一樣源,就沒法拿到對方的DOM,典型的例子是iframe
窗口和window.open
方法打開的窗口,若是和父窗口不一樣源,則會報錯。canvas
此時若是兩個窗口一級域名相同,只是二級域名不一樣,那麼設置document.domain
屬性,就能夠規避同源策略。
而對於徹底不一樣源的網站,目前有三種方法能夠解決跨域窗口之間的通訊問題。
片斷標識符(fragment identifier)指的是URL的#後面的部分,即http://store.company.com/dir/other.html#fragment
的#fragment
(location.hash),若是隻改變片斷標識符,頁面不會從新刷新。
父窗口能夠把信息寫入子窗口的片斷標識符,子窗口經過監聽hashchange
事件獲得通知。
每一個iframe都有包裹它的window,這個window是top window的子窗戶,因此天然有window.name
屬性,指的是當前窗口的名字,這個屬性的最大特色是,不管是否同源,只要在同一個窗口裏,窗口內全部頁面對window.name都有讀寫的權限。
window.name的值只能是字符串的形式,這個字符串的最大能容許2M左右甚至更大的一個容量,具體取決於不一樣的瀏覽器。
例如,想要在http://example/a.html
中獲取http://company.com/data.html
中的數據,能夠在a.html中使用一個隱藏的iframe,將iframe的src首先設置爲http://company.com/data.html
,將其window.name設置爲所需的數據內容,隨後再將這個iframe的src設置爲跟a.html頁面同一個域的一個頁面,否則a.html獲取不到該iframe的window.name
這是html5中新引入的一個API,可使用它向其它的window對象發送消息,不管這個window對象屬於同源仍是不一樣源。
例如,父窗口http://example/a.html
向子窗口http://company.com/data.html
發送消息:
var newWin = window.open('http://company.com/data.html', 'title') newWin.postMessage('Hello World!'. 'http://company.com/data.html')
window.postMessage
方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源,即協議
+端口
+域名
,也能夠設置爲*
,表示不限制域名。
子窗口向父窗口發送消息的寫法相似:
window.opener.postMessage('Nice to see you', 'http://example/a.html')
子窗口和父窗口均可以經過message
時間,監聽對方的消息。
window.addEventListener('message', function(e) { // ... }, false)
message
事件的事件對象event
有如下三個屬性:
經過window.postMessage
,也能夠讀寫其餘窗口的localStorage
同源策略規定,AJAX請求只能發給同源的網址,不然就報錯,可是有三種方法能夠規避這個限定:
JSONP是服務器與客戶端跨源通訊的經常使用方法。基本思想是利用<script>
請求腳本可以跨域訪問的特性,先定義了一個回調方法,而後將其做爲url參數的一部分發送到服務端,服務端經過字符串拼接的方式將數據包裹在回調方法中,再返回回來。
// 網頁動態插入`<script>`元素 function addScriptTag(src) { var script = document.createElement("script") script.setAttribute("type", "text/javascript") srcipt.src = src document.body.appendChild(script) } window.onload = function() { addScriptTag('http://example.com/ip?callback=foo') } function foo(data) { // ... }
WebSocket是一種通訊協議,使用ws://
(非加密)和wss://
(加密)做爲協議前綴。該協議不實行同源政策,只要服務支持,就能夠經過它進行跨源通訊。
瀏覽器發出的WebSocket請求的頭信息中含有Origin
字段,表示該請求的請求源,即發自哪一個域名。(加入白名單)
跨域資源共享(Cross-Origin Resource Sharing,CORS)是一種使用額外的HTTP頭來使一個用戶代理從一個不一樣於當前站點(域)的服務器獲取指定的資源的機制。用戶代理使用跨域HTTP請求來獲取與當前文檔不一樣域、不用協議或端口的資源。
出於安全考慮,瀏覽器會限制從腳本內發起的跨域HTTP請求。而跨域資源共享(CORS)機制容許web應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。瀏覽器支持在API容器中(例如XMLHttpRequest
或Fetch
)使用CORS,以下降跨域HTTP請求所帶來的風險。
跨域資源共享標準容許在下列場景中使用跨域HTTP請求:
@font-face
使用跨域字體資源),所以,網站就能夠發佈TrueType字體資源,並只容許已受權網站進行跨站調用;瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)
只要同時知足如下兩大條件,就屬於簡單請求:
1 請求方法是如下三種方法之一:
- HEAD - GET - POST
2 HTTP的頭信息不超出如下幾種字段:
- Accept - Accept-Language - Content-Language - Last-Event-ID - Content-Type:(只限三個值:application/x-www-form-urlencoded、multipart/from-data、text/plain)
只要不一樣時知足上面兩個條件,就屬於非簡單請求
對於簡單請求,瀏覽器會在請求頭部增長一個Origin
字段。這個字段用來講明本次請求來自哪一個源(協議+域名+端口)。服務器根據這個值決定是否贊成此次請求。
若是Origin
指定的源不在許可範圍內,服務器會返回一個正常的HTTP迴應。而這個迴應的頭信息不包含Access-Control-Allow-Origin
字段,從而會拋出錯誤被XMLHttpRequest
的onerror
函數捕獲,(迴應的狀態碼有多是200)。
若是Origin
指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段
Access-Control-Allow-Origin: ... Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: callback Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin
: 必須。值要麼是請求時Origin
的值,要麼是'*'Access-Control-Allow-Credentials
: 可選。布爾值,決定是否容許發送Cookie,不須要則刪除該字段。Access-Control-Expose-Headers
: 可選。CORS請求時,XMLHttpRequest
對象的 getResponseHeader()
方法只能拿到6個基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。若是想拿到其餘字段,就須要在Access-Control-Expose-Header
裏面指定。上面的例子指定爲callback,則可使用getResponseHeader(callback)
獲取callback字段的值。CORS請求默認不發送Cookie和HTTP認證信息。若是要把Cookie發送到服務器,一方面要服務器贊成,指定Access-Control-Allow-Credentials
字段,另外一方面,開發者須要在AJAX請求中設置withCredentials
屬性:
var xhr = new XMLHttpRequest() xhr.withCredentials = true
不然,即便服務器贊成發送Cookie,瀏覽器也不會發送。
須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin
就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源策略,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且跨域的原網頁中的document.cookie
操做也沒法獲取嗷服務器域名下的Cookie。
非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUT
或DELETE
,或者Content-Type
字段的類型是application/json
。
非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲「預檢」請求。
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP操做和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest
請求,不然就報錯。
「預檢」請求用請求方法是OPTIONS
,表示這個請求是用來詢問的。頭信息裏面,關鍵字段是Origin
,表示請求來自哪一個源。
還有如下兩個特殊字段:
Access-Control-Request-Method
: 必須。列出非簡單請求的請求類型Access-Control-Request-Headers
: 非簡單請求額外攜帶的頭信息字段。服務器返回的響應:
Access-Control-Allow-Methods: ... Access-Control-Expose-Headers: callback Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
Access-Control-Request-Headers
字段,則Access-Control-Allow-Headers
字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。CORS與JSONP的使用目的相同,可是比JSONP更強大。
JSONP只支持GET
請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。