常見的跨域請求以及解決方案 jsonp cors

日常在前端開發工作中跨域問題是很正常是的事情,因為稍大一些的項目你總不能實時的保存到服務器然后線上預覽開發,影響線上環境又不方便開發。

其實跨域問題主要是存在開發環境中,生成環境已經布置到服務器上了不存在跨域問題。

常用的有幾種方式解決跨域的問題。

1、用構建工具,好多團隊也是用這種方式解決的

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://www.baidu.com/',
        pathRewrite: {'^/api' : ''},
        changeOrigin: true,     // target是域名的話,需要這個參數,
      },
      '/api2': {
          .....
      }
    }
  }
};

這段代碼的配置 捕獲api標識 比如http://localhost:8080/api/users 前端發起這個請求
里面包含api字段 就會被捕獲到 然后執行配置

target

代理的API地址 可以理解成要替換更改成的域名
可以是域名也可以是ip 是域名下面changeOrigin要填寫成true

pathRewrite

路徑重寫 比如現在這個例子 前端訪問http://localhost:8080/api/users 這個接口
其實會轉化成 http://www.baidu.com/users

changeOrigin

這個參數可以讓target參數是域名

2、jsonp前端跨域請求

說jsonp 之前先來說下了為什么會出現跨域請求,好端端的很歡快的敲得代碼,很正常的ajax請求為什么會出現跨域請求,是因為同源策略。
同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權的情況下,不能讀寫對方資源。
地址里面的協議、域名和端口號均相同則屬于同源。
三點 協議(http和https)、域名、端口全部相同可訪問,其中有一項不同就會受到同源策略的影響,也能想的通啦,你的接口誰都能使用肯定不安全,這也是為什么項目實時保存到服務器上預覽開發不受跨域影響的原因(都放服務器上了,自己訪問自己肯定是同源咯)。
但再思考一個問題,jq cnd這大家都經常用吧,cnd不在咱們的本地也不在服務器上為啥能訪問內,img標簽使用網絡圖片,圖片也是在別人的服務器上為啥能用內,
因為img、iframe 、script 及其他多媒體標簽不受同源策略的影響,嘿是不是漏出了邪惡的笑容,程序員這個行業啊一旦出現什么什么不受影響那就是告訴你,這里可以搞事。
jsonp也就是用的這個原理 動態創建加載一個 script標簽,script標簽的src就是后臺的接口地址然后和后臺預定好一個回調函數字段,一般就是callback,最后再確定下來要傳給后臺的字段
把這些拼接起來
例如http://www.a.com/test/index.php 這是接口 callback 對應的回調函數是foo 數據是 name = 'tianyu'
拼接的結果 http://www.a.com/test/index.php?callback=foo&name='tianyu'
把拼接結果放到動態創建的script上 這個scriptt標簽就會自動加載src路徑后臺就能獲取到請求
php代碼

$jsonData = getDataAsJson($_GET['symbol']);    //組織好發送字段
echo $_GET['callback'] . '(' . $jsonData . ');';        //拼接參數

注意看這里的后臺代碼,前端script請求的src是個.php文件 后臺接受到了后先是拼接返回的數據,然后具體返回的是 函數名(拼接的參數),這是前端拿到的script的內容就是 foo(后臺傳遞進來的參數)
前端的foo(data) 函數中就會被調用并且data就是 后臺拼接好的字段給你。
這里如果沒有立即理解就再仔細想想,原理不難就是通過script標簽的形式發起一個請求,后臺接受到了請求后組裝信息,信息組裝好前端加載到的就是一個js文件,js里有你定義好的函數以及需要的數據,并且瀏覽器默認執行新加載的js你也就順理成章的完成了這次請求,拿到了想要的數據。
這就是jsonp的原理 利用部分標簽不受同源策略的影響去加載接口 當然都說到這一步了你肯定也能想到jsonp只支持get請求不接受post jquery也是這樣 jq只是更好的封裝了下肯定不能更改這jsonp的原理

$.ajax({
    url: 'http://192.168.1.114/yii/demos/test.php', //不同的域
    type: 'GET', // jsonp模式只有GET 是合法的
    data: {
        'action': 'aaron'
    },
    dataType: 'jsonp', // 數據類型
    jsonp: 'callback', // 指定回調函數名,與服務器端接收的一致,并回傳回來
})

jq只是幫助了你更好的封裝 內部做好一些清除緩存的機制(就是每次請求callback對應的回調函數不一樣)
這就是jsonp的內部原理機制。

3、再說一種跨域請求的方式 后端解決的辦法 專業名叫 CORS

前端不用做什么跨域方面的準備 主要工作是在后端這一塊
不過在這里也會簡單的介紹一下 xhr(XMLHttpRequest)
后端這里我就拿node舉例子
先說xhr 畢竟前端發起請求后端接受 先說發起這一塊
xhr其實就是我們常用到的ajax, jq的$.ajax 底層封裝的就是xhr
現在網上也有fetch (新一代的ajax api),也看了一些文章 ajax已死 fetch永生什么的 首先必須承認的是文章寫得非常好 fetch 好處也是非常多
語法簡潔,更加語義化
基于標準 Promise 實現,支持 async/await
可以鏈式調用
相對于xhr 沒有回調地獄 符合關注分離原則
等等 不多說
但我感覺xhr 用的也挺舒服 至于回調地獄也可以用封裝的手法外層加上Promise而且兼容強,主流瀏覽器都支持 現在網上好多fetch的js封裝都要有判斷 當前瀏覽器是否支持fetch 不支持還是要xhr
這里不多說本文不討論fetch和xhr誰好誰壞,個人用的舒服就行,本文還是用xhr說前端請求這一塊。

//第一步創建xhr
let xhr = new XMLHttpRequest();
//第二步 定義xhr 的狀態返回碼  用于判斷請求是否成功
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        if(xhr.status >= 200 && xhr.status < 300){
            try {
                let res = JSON.parse(xhr.responseText);
                callback(res)
            } catch (e) {
                let msg = "請求數據失敗,返回的不是JSON";
                console.log(msg,e);
                errorCallback && errorCallback({
                    retcode: xhr.status,
                    msg: msg
                });
            }
        }
    }
}
//其他的method 就不演示了 每個項目有自己的規則例如get寫到url上 post寫到data里或者其他的url上加token之類的 
//這些每個項目都有不同需要和后端商定好自定義寫邏輯就行
//第三步  初始化http請求參數
xhr.open(method, url, true);  //請求方式  請求地址   是否異步
//第四部 發送http請求
xhr.send();

暫時先這個樣子簡易的ajax請求就發起了
然后就很順利的就能看到瀏覽器報錯了。


image.png

錯誤信息基本是長這樣子的大概意思是請求頭檢測沒有通過

this.header("Access-Control-Allow-Origin", this.header("origin") || "*");
(這個只是參考 node的寫法 其他的根據不同語言寫法有差異)
很簡單加上這一句就能解決請求跨域問題
這個是用來指定那些客戶端的域名可以訪問該資源 填寫通配符 * 就是全部或是填寫一個完整的url
已經可以愉快的發起請求了

不過這只是最簡單的請求方式 忽然有一天后端人員找你 需要加上在HTTP的頭部增加些信息,可能是增加一些安全類的簽名或是用戶登錄的token 或是多樣性請求方式等等啦就會發現又出問題了,


image.png

明明發送的是get請求但這里顯示是 OPTIONS
其實并不是你的請求變成了OPTIONS 而是多發送了一個請求你寫好的真正的請求還沒有發起。
OPTIONS這次請求有人稱為"預檢"請求 也有人叫他 "嗅探"請求
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

那什么情況下回出現這次OPTIONS請求呢?
復雜請求時 復雜請求 和 簡單請求的區別在于簡答請求只有簡單的get/head/post請求,且不能自定義http的headers,一旦你發送的是put、delete之類或是增加了headers就會變成復雜請求也就會多發少一次OPTIONS請求,這是瀏覽器默認的無法阻止的。

解決辦法也很簡單沒有想得這么復雜,同樣也是后臺同學幫忙解決。
讓默認第一次發送的OPTIONS請求通過即可解決,OPTIONS接到了請求成功的通知后會立即發送真正的請求。

this.header("Access-Control-Allow-Origin", this.header("origin") || "*");
//那些域名請求可以通過
this.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
//服務器支持的請求的方法
this.header("Access-Control-Allow-Headers", "content-type,x-requested-with,x-token");
//服務器支持的所有頭信息字段
this.header('Access-Control-Allow-Credentials',true);
//允許客戶端攜帶驗證信息,例如 cookie 之類的
if(this.method == 'OPTIONS'){
    return this.success({},'讓你通過,正式請求趕緊來吧');
  //不處理 OPTIONS請求 讓其通過就行
}

一樣的上面這些是node 的寫法,其他語言會略有不同但基本字段是這些設置上以后就可以完美解決跨域問題

日常開發時第三種用的方式比較多一些,畢竟設置起來不是很困難并且能有效的解決問題。
基本的跨域請求我在項目中用到也就是這些。
然后咱們再來說說和請求關系的cookie以及session,下一篇文章寫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。