javascript
jsonp-反向代理-CORS解决JS跨域问题的个人总结
jsonp-反向代理-CORS解決JS跨域問(wèn)題的個(gè)人總結(jié)
網(wǎng)上說(shuō)了很多很多,但是看完之后還是很混亂,所以我自己重新總結(jié)一下。解決 js 跨域問(wèn)題一共有8種方法,
各個(gè)方法都有各自的優(yōu)缺點(diǎn),但是目前前端開(kāi)發(fā)方面比較常用的是 jsonp,反向代理,CORS:
-
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫(xiě)。它是W3C標(biāo)準(zhǔn),是跨源AJAX請(qǐng)求的根本解決方法。優(yōu)點(diǎn)是正統(tǒng),符合標(biāo)準(zhǔn),缺點(diǎn)是:
- 但是需要服務(wù)器端配合,比較麻煩。
-
JSONP 優(yōu)點(diǎn)是對(duì)舊式瀏覽器支持較好,缺點(diǎn)是:
- 但是只支持 get 請(qǐng)求。
- 有安全問(wèn)題(請(qǐng)求代碼中可能存在安全隱患)。
- 要確定jsonp請(qǐng)求是否失敗并不容易
-
反向代理都能夠兼容以上的確定,但是僅僅作為前端開(kāi)發(fā)模式的時(shí)候使用,在正式上線(xiàn)環(huán)境較少用到。
- 因?yàn)殚_(kāi)發(fā)環(huán)境的域名跟線(xiàn)上環(huán)境不一樣才需要這樣處理。
- 如果線(xiàn)上環(huán)境太復(fù)雜,本身也是多域(后面說(shuō)到的同源策略問(wèn)題,多子域,或者多端口問(wèn)題),那么需要采用 jsonp 或者 CORS 來(lái)處理。
一、什么是跨域問(wèn)題
跨域問(wèn)題一般只出現(xiàn)在前端開(kāi)發(fā)中使用 javascript 進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候,瀏覽器為了安全訪(fǎng)問(wèn)網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)而進(jìn)行的限制。
提示的錯(cuò)誤大致如下:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://XXXXXX' is therefore not allowed access.
二、為什么會(huì)出現(xiàn)跨域問(wèn)題
因?yàn)闉g覽器收到同源策略的限制,當(dāng)前域名的js只能讀取同域下的窗口屬性。
2.1 同源策略
同源指的是三個(gè)源頭同時(shí)相同:
- 協(xié)議相同
- 域名相同
- 端口相同
舉例來(lái)說(shuō),http://www.example.com/dir/page.html這個(gè)網(wǎng)址,
協(xié)議是 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:不同源(端口不同)同源策略限制了以下行為:
- Cookie、LocalStorage 和 IndexDB 無(wú)法讀取
- DOM 和 JS 對(duì)象無(wú)法獲取
- Ajax請(qǐng)求發(fā)送不出去
詳細(xì)的同源策略相關(guān),可以參考http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
三、解決跨域問(wèn)題
3.1 反向代理方式
反向代理和正向代理的區(qū)別:
- 正向代理(Forward Proxy),通常都被簡(jiǎn)稱(chēng)為代理,就是在用戶(hù)無(wú)法正常訪(fǎng)問(wèn)外部資源,比方說(shuō)受到GFW的影響無(wú)法訪(fǎng)問(wèn)twitter的時(shí)候,我們可以通過(guò)代理的方式,讓用戶(hù)繞過(guò)防火墻,從而連接到目標(biāo)網(wǎng)絡(luò)或者服務(wù)。
- 反向代理(Reverse Proxy)是指以代理服務(wù)器來(lái)接受 Internet 上的連接請(qǐng)求,然后將請(qǐng)求轉(zhuǎn)發(fā)給內(nèi)部網(wǎng)絡(luò)上的服務(wù)器,并將從服務(wù)器上得到的結(jié)果返回給 Internet 請(qǐng)求連接的客戶(hù)端,此時(shí),代理服務(wù)器對(duì)外就表現(xiàn)為一個(gè)服務(wù)器。
那么我們可以理解為反向代理
如何使用反向代理服務(wù)器來(lái)達(dá)到跨域問(wèn)題解決:
- 前端ajax請(qǐng)求的是本地反向代理服務(wù)器
-
本地反向代理服務(wù)器接收到后:
- 修改請(qǐng)求的 http-header 信息,例如 referer,host,端口等
- 修改后將請(qǐng)求發(fā)送到實(shí)際的服務(wù)器
- 實(shí)際的服務(wù)器會(huì)以為是同源(參考同源策略)的請(qǐng)求而作出處理
現(xiàn)在前端開(kāi)發(fā)一般使用 nodejs來(lái)做本地反向代理服務(wù)器
// 在 express 之后引入路由 var app = express();var apiRoutes = express.Router();app.use(bodyParser.urlencoded({extended:false}))// 自定義 api 路由 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: { // 修改 headerreferer: "https://c.y.qq.com/",host: "c.y.qq.com"},params: req.query}).then((response) => {var ret = response.dataif (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)}) });// 使用這個(gè)路由 app.use("/api", apiRoutes);3.2 JSONP 方式
JSONP有些文章會(huì)叫動(dòng)態(tài)創(chuàng)建script,因?yàn)樗_實(shí)是動(dòng)態(tài)寫(xiě)入 script 標(biāo)簽的內(nèi)容從而達(dá)到跨域的效果:
- AJAX 無(wú)法跨域是受到“同源政策”的限制,但是帶有src屬性的標(biāo)簽(例如<script>、<img>、<iframe>)是不受該政策限制的,因此我們可以通過(guò)向頁(yè)面中動(dòng)態(tài)添加<script>標(biāo)簽來(lái)完成對(duì)跨域資源的訪(fǎng)問(wèn),這也是 JSONP 方案最核心的原理,換句話(huà)理解,就是利用了【前端請(qǐng)求靜態(tài)資源的時(shí)候不存在跨域問(wèn)題】這個(gè)思路。
- JSONP(JSON with Padding)是數(shù)據(jù)格式JSON的一種“使用模式”。
- JSONP 只能用 get 方式。
實(shí)現(xiàn) jsonp 的方式:
引用來(lái)自https://segmentfault.com/a/1190000012469713的圖
- 客戶(hù)端和服務(wù)器端約定一個(gè)參數(shù)名是代表 jsonp 請(qǐng)求的,例如約定 callback 這個(gè)參數(shù)名。
- 然后服務(wù)器端準(zhǔn)備好針對(duì)之前約定的 callback 參數(shù)請(qǐng)求的 javascript 文件,這個(gè)文件里面要有一個(gè)函數(shù)名,要跟客戶(hù)端請(qǐng)求的時(shí)候的函數(shù)名要保持一致。(如下面例子:ip.js)
- 然后客戶(hù)端注冊(cè)一個(gè)本地運(yùn)行的函數(shù),并且函數(shù)的名字要跟去請(qǐng)求服務(wù)器進(jìn)行 callback 回調(diào)的函數(shù)的名字要一致。(如下面例子:foo 函數(shù)跟請(qǐng)求時(shí)候callback=foo的名字是一致的)
- 然后客戶(hù)端對(duì)服務(wù)器端進(jìn)行 jsonp 的方式請(qǐng)求。
- 服務(wù)器端返回剛才配置好的js 文件(ip.js)到客戶(hù)端
-
客戶(hù)端瀏覽器,解析script標(biāo)簽,并執(zhí)行返回的javascript文件,此時(shí)數(shù)據(jù)作為參數(shù),傳入到了客戶(hù)端預(yù)先定義好的 callback 函數(shù)里。
- 相當(dāng)于本地執(zhí)行注冊(cè)好foo 函數(shù),然后獲取了一個(gè)foo 函數(shù),并且這個(gè)獲取的 foo 函數(shù)里面包含了傳入的參數(shù)(例如 foo({XXXXX}))
這是一個(gè)實(shí)例 demo:
服務(wù)器端文件ip.js
foo({"ip": "8.8.8.8" });客戶(hù)端文件 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>// 動(dòng)態(tài)插入 script 標(biāo)簽到 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');}// 執(zhí)行本地的 js 邏輯,這個(gè)要跟獲取到的 jsonp 文件的函數(shù)要一致function foo(data) {console.log('Your public IP address is: ' + data.ip);};</script> </head> <body> </body> </html>3.3 CORS 方式
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱(chēng)是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制。
- CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
- 整個(gè)CORS通信過(guò)程,都是瀏覽器自動(dòng)完成,不需要用戶(hù)參與。對(duì)于開(kāi)發(fā)者來(lái)說(shuō),CORS通信與同源的AJAX通信沒(méi)有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶(hù)不會(huì)有感覺(jué)。
3.3.1 CORS的請(qǐng)求分為兩類(lèi):
- 簡(jiǎn)單請(qǐng)求
- 非簡(jiǎn)單請(qǐng)求
只要同時(shí)滿(mǎn)足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求。
(1) 請(qǐng)求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時(shí)滿(mǎn)足上面兩個(gè)條件,就屬于非簡(jiǎn)單請(qǐng)求。
3.3.2 簡(jiǎn)單請(qǐng)求
如果是簡(jiǎn)單請(qǐng)求的話(huà),會(huì)自動(dòng)在頭信息之中,添加一個(gè)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...這個(gè)Origin對(duì)應(yīng)服務(wù)器端的Access-Control-Allow-Origin設(shè)置,所以一般來(lái)說(shuō)需要在服務(wù)器端加上這個(gè)Access-Control-Allow-Origin 指定域名|*
3.3.3 非簡(jiǎn)單請(qǐng)求
如果是非簡(jiǎn)單請(qǐng)求的話(huà),會(huì)在正式通信之前,增加一次HTTP查詢(xún)請(qǐng)求,稱(chēng)為"預(yù)檢"請(qǐng)求(preflight)。
瀏覽器先詢(xún)問(wèn)服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)。
需要注意這里是會(huì)發(fā)送2次請(qǐng)求,第一次是預(yù)檢請(qǐng)求,第二次才是真正的請(qǐng)求!首先發(fā)出預(yù)檢請(qǐng)求:
// 預(yù)檢請(qǐng)求 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字段,"預(yù)檢"請(qǐng)求的頭信息包括兩個(gè)特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來(lái)列出瀏覽器的CORS請(qǐng)求會(huì)用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
該字段是一個(gè)逗號(hào)分隔的字符串,指定瀏覽器CORS請(qǐng)求會(huì)額外發(fā)送的頭信息字段,上例是X-Custom-Header。
然后服務(wù)器收到"預(yù)檢"請(qǐng)求以后:
檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認(rèn)允許跨源請(qǐng)求,就可以做出回應(yīng)。
// 預(yù)檢請(qǐng)求的回應(yīng) 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最后一旦服務(wù)器通過(guò)了"預(yù)檢"請(qǐng)求:
以后每次瀏覽器正常的CORS請(qǐng)求,就都跟簡(jiǎn)單請(qǐng)求一樣,會(huì)有一個(gè)Origin頭信息字段。服務(wù)器的回應(yīng),也都會(huì)有一個(gè)Access-Control-Allow-Origin頭信息字段。
// 以后的請(qǐng)求,就像拿到了通行證之后,就不需要再做預(yù)檢請(qǐng)求了。 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
參考文檔:
- 前端解決跨域問(wèn)題的8種方案
- 瀏覽器同源政策及其規(guī)避方法
- https://tonghuashuo.github.io/blog/jsonp.html
- http://www.cnblogs.com/yuzhongwusan/archive/2012/12/11/2812849.html
- http://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html
- https://segmentfault.com/a/1190000002438126
總結(jié)
以上是生活随笔為你收集整理的jsonp-反向代理-CORS解决JS跨域问题的个人总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: leetcode 66 Plus One
- 下一篇: javascript杂记