網上說了不少不少,可是看完以後仍是很混亂,因此我本身從新總結一下。
解決 js 跨域問題一共有8種方法:javascript
各個方法都有各自的優缺點,可是目前前端開發方面比較經常使用的是 jsonp,反向代理,CORS:php
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標準,是跨源AJAX請求的根本解決方法。html
JSONP的核心則是動態添加<script>
標籤來調用服務器提供的js腳本。前端
反向代理都可以兼容以上的肯定,可是僅僅做爲前端開發模式的時候使用,在正式上線環境較少用到。java
這裏主要說明這三種方式。其餘方式暫不說明。
跨域問題通常只出如今前端開發中使用 javascript 進行網絡請求的時候,瀏覽器爲了安全訪問網絡請求的數據而進行的限制。node
提示的錯誤大體以下:jquery
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://XXXXXX' is therefore not allowed access.
由於瀏覽器受到同源策略的限制,當前域名的js只能讀取同域下的窗口屬性。ios
換句話來講,就是跨越了瀏覽器的同源策略限制的時候,就會觸發了咱們所說的「跨域」問題。git
同源指的是三個源頭同時相同:github
舉例來講,http://www.example.com/dir/page.html
這個網址,
協議是 http:// 域名是 www.example.com 端口是80 //它的同源狀況以下: http://www.example.com/dir2/other.html:同源 http://example.com/dir/other.html:不一樣源(域名不一樣) http://v2.www.example.com/dir/other.html:不一樣源(域名不一樣) http://www.example.com:81/dir/other.html:不一樣源(端口不一樣)
總的來講,只要不是三者同時相同,那麼就不是同源,那麼就會觸發同源策略限制。
限制了:
這就是咱們日常所說的「跨域問題」。
詳細的同源策略相關,能夠參考http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
反向代理和正向代理的區別:
那麼能夠利用反向代理的原理,咱們經過一箇中間代理服務器(反向代理服務器),將客戶端網絡請求的一些 host,domain,port 和協議等東西進行改寫,使其模擬爲能夠訪問目標服務器的請求,模擬成不觸犯同源策略的請求去請求目標服務器。
本地反向代理服務器接收到後:
如今前端開發通常使用 nodejs來作本地反向代理服務器
// 在 express 以後引入路由 var app = express(); var apiRoutes = express.Router(); app.use(bodyParser.urlencoded({extended:false})) // 訪問反向代理的路由地址 apiRoutes.get("/lyric", function (req, res) { var url = "https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg"; // 改寫源端的請求信息,而後重定向到目標服務器 axios.get(url, { headers: { // 修改 header referer: "https://c.y.qq.com/", host: "c.y.qq.com" }, params: req.query }).then((response) => { var ret = response.data if (typeof ret === "string") { var reg = /^\w+\(({[^()]+})\)$/; var matches = ret.match(reg); if (matches) { ret = JSON.parse(matches[1]) } } res.json(ret) }).catch((e) => { console.log(e) }) }); // 使用這個路由 app.use("/api", apiRoutes);
這段代碼的執行原理是:
JSONP有些文章會叫動態建立script,由於他確實是動態寫入 script 標籤的內容從而達到跨域的效果:
<script>、<img>、<iframe>
)是不受該政策限制的,所以咱們能夠經過向頁面中動態添加<script>
標籤來完成對跨域資源的訪問,這也是 JSONP 方案最核心的原理,換句話理解,就是利用了前端請求靜態資源的時候不存在跨域問題這個思路。簡單一點的例子:
經過不受同源策略限制的標籤,例如 script,將一段js代碼間接地從外部引入。經過script標籤向目標源發起一個GET請求,服務器根據請求的參數返回包含js的代碼。
//本地代碼 <script> // 這個函數名字跟服務器返回的那段 js 的函數名字是同樣的,因此可以實現調用 function getData(obj) { // 參數是一個對象 var data = JSON.parse(obj); console.log(data.name);//jiavan console.log(data.age);//20 } </script> <script src="http://cv.jiavan.com/test/data.php?callback=getData"></script> //服務器上的代碼 <?php $func = $_GET['callback']; $data = '{"name": "jiavan", "age": 20}'; echo $func."(".$data.");"; ?> // 服務器返回的數據是一段 js 代碼 getData( // 這是 js 的函數寫法 { // 這是參數,參數是一個對象 "name":"jiavan", "age": 20 } )
先在本地定義了一個函數,這是用來處理來自服務器上數據的函數,下面用一個script標籤,而且向服務器發起了一個GET請求,而且指定了處理數據的回調函數,即上方的getData,服務器收到請求後返回了getData('{"name": "jiavan", "age": 20}');
,即便一段js代碼,將數據傳入到回調函數中處理,這樣便完成了跨域。
參考:http://www.javashuo.com/article/p-fudpqfru-ev.html
複雜一點的例子:
引用來自http://www.javashuo.com/article/p-ayiskmas-bm.html的圖
ip.js
)callback=foo
的名字是一致的)jsonp 的方式
請求。ip.js
)到客戶端客戶端瀏覽器,解析script標籤,並執行返回的javascript文件,此時數據做爲參數,傳入到了客戶端預先定義好的 callback 函數裏。
foo({XXXXX})
)服務器端文件ip.js
foo({ "ip": "8.8.8.8" });
客戶端文件 jsonp.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script> // 動態插入 script 標籤到 html 中 function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } // 獲取 jsonp 文件 window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } // 執行本地的 js 邏輯,這個要跟獲取到的 jsonp 文件的函數要一致 function foo(data) { console.log('Your public IP address is: ' + data.ip); }; </script> </head> <body> </body> </html>
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它容許瀏覽器向跨源服務器,發出XMLHttpRequest
請求,從而克服了AJAX只能同源使用的限制。
所以,實現CORS通訊的關鍵是服務器端。只要服務器端實現了CORS接口,就能夠跨源通訊。
只要同時知足如下兩大條件,就屬於簡單請求。
(1) 請求方法是如下三種方法之一:
(2)HTTP的頭信息不超出如下幾種字段:
凡是不一樣時知足上面兩個條件,就屬於非簡單請求。
若是是簡單請求的話,會自動在頭信息之中,添加一個Origin字段,Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否贊成此次請求。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin
字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest
的onerror
回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。
這個Origin對應服務器端的Access-Control-Allow-Origin
設置,因此通常來講須要在服務器端加上這個Access-Control-Allow-Origin
便可,相似這種:
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
若是是非簡單請求的話,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP動詞和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。
須要注意這裏是會發送2次請求,第一次是預檢請求,第二次纔是真正的請求!
首先發出預檢請求:
// 預檢請求 OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0..
除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header。
而後服務器收到"預檢"請求之後:
檢查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段之後,確認容許跨源請求,就能夠作出迴應。
// 預檢請求的迴應 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
最後一旦服務器經過了"預檢"請求:
之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。服務器的迴應,也都會有一個Access-Control-Allow-Origin頭信息字段。
// 之後的請求,就像拿到了通行證以後,就不須要再作預檢請求了。 PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
詳情參考這裏http://www.ruanyifeng.com/blog/2016/04/cors.html
總的來講,只須要知道2個地方便可,其餘的能夠舉一反三:
Access-Control-XXX
的處理,目的是爲了處理請求的來源判別。參考文檔: