博客 / 詳情

返回

Ajax跨域請求

1、Ajax 請求限制

Ajax 只能向自己的服務器發送請求

比如現在有一個A網站,一個B網站,A網站中的HTML文件只能向A網站服務器發送Ajax請求,B網站中的HTML文件只能向B網站服務器發送Ajax請求,但是A網站時不能向B網站發送Ajax請求的,同理,B網站不能向A網站發送Ajax請求的

A網站先B網站發送Ajax請求,我們稱之為跨域請求

如:在本地打開AJax文件先服務器請求內容

img

上面報錯熟不熟悉,這就是因為發生了跨域請求導致的錯誤

2、什麼是同源

瀏覽器為了安全問題,做出了同源策略的限制,即跨域請求默認是不被允許的

這裏所説的同源是指:協議名、主機號、端口號 這三部分是一樣。相同域之間的請求是不受限制的,而不同域之間不能互相請求的,這就是Ajax的同源策略

img

如果兩個頁面擁有相同的協議、域名和端口,那麼兩個頁面就屬於同一個源,其中只要有一個不同,就是不同源

img

同源策略是為了保護用户信息的安全,防止惡意的網站竊取數據,最初的同源策略是指A網站在客户端設置Cookie,B網站則不能訪問的

3、解決跨域請求

Ajax默認是不允許進行跨域請求的,但是我們卻時常進行非同源請求,擺在我們面前的就是解決跨域請求

解決跨域請求有4種方法:

  1. JSONP 解決跨域請求
  2. CORS 跨域資源共享
  3. 代理服務器
  4. 基於 <iframe> 標籤

主要了解前兩種

3.1 JSONP 解決跨域請求

JSONP 不屬於Ajax請求,但是它可以模擬 Ajax 請求

有沒有想過,我們平時在HTML頁面中的 <img><link><script> 等標籤中的src屬性不受跨域請求的,所以我們是否也可以讓 <script> 去請求服務器的JS文件來獲取數據嗎?

of course 當然

分為三步走:

  1. 將不同源的服務器請求地址寫在<script> 標籤的src屬性中
<script src="http://127.0.0.1:3000/test"></script>
  1. 服務端響應數據必須是一個函數的調用,真正要發送給客户端的數據需要作為函數調用的參數
const data = "getData({namer: "周深", song: "大魚"})";
res.rend(data);  // 發送數據
  1. 全局作用域下定義函數(兩函數名要一樣)
function getData(options) {
    alert(options.namer + '唱了一首' + options.song)
}

栗子:

客户端:

<body>
    <script>
        function getData(data) {
            alert(data.namer + '唱了一首' + data.song)
        }
    </script>
    <script src="http://127.0.0.1:3000/test"></script>
</body>

服務器端:

app.get('/test', function(req, res) {
    let data = 'getData({namer: "周深", song: "大魚"})'
    res.send(data)
})

JSONP 優化

第一種:把函數名通過URL傳給服務端

我們可以把本地函數通過URL傳參 ?callback=getData 的方式,讓服務器知道本地函數的名字

這樣的好處就是客户端修改函數名,服務器就不受影響

<script>
    function getData(data) {
        alert(data.namer + '唱了一首' + data.song)
    }
</script>
<script src="http://127.0.0.1:3000/test?callback=getData></script>
app.get('/test', function(req, res) {
    res.jsonp({ namer: '薛之謙', song: '演員' })
})

jsonp 函數已經幫我們做好了處理,會自動獲取參數中callback 的值作為函數,所以服務端只需要傳遞數據即可

第二種:將script請求的變成動態請求

<button>按鈕</button>

<script>
    function getData(data) {
        alert(data.namer + '唱了一首' + data.song)
    }
</script>

<script type="text/javascript">
    let btn = document.querySelector('button');
    btn.onclick = function() {
        let script = document.createElement('script');
        script.src = 'http://127.0.0.1:3000/test';
        document.body.appendChild(script);
        // 為script添加onload事件,過河拆橋
        script.onload = function() {
            document.body.removeChild(script)
        };
    };
</script>

發送請求的時候動態創建script標籤發送,結束之後隨即把它刪除了

但是每次我們都要寫這麼一坨代碼嗎?説這句話的時候就知道又要瘋轉了

下面我們來封裝一個jsonp函數吧

function jsonp(options) {
    // 創建標籤
    let script = document.createElement('script')

    // 拼接字符串
    let params = '';
    for (let key in options.data) {
        params += '&' + key + '=' + options.data[key]
    }

    // 創建隨機函數,toString將數字轉換字符串去除小數點.
    let fnName = 'myFn' + Math.random().toString().replace('.', '');
    // fnName不是全局函數,利用window將其變成函數
    window[fnName] = options.success;

    script.src = options.url + '?callback=' + fnName + params;
    document.body.appendChild(script)

    // 為script標籤添加onload事件
    script.onload = function() {
        // 刪除掉script標籤
        document.body.removeChild(script)
    }
}

調用jsonp(需要自行導入jsonp函數)

<body>
    <button id="xzq">薛之謙</button>
    <button id="zs">周深</button>
    <script type="text/javascript">
        let btn1 = document.querySelector('#xzq');
        let btn2 = document.querySelector('#zs');
        
        btn1.onclick = function() {
            jsonp({
                url: 'http://127.0.0.1:3000/test',
                data: {
                    namer: '薛之謙',
                    song: '剛剛好'
                },
                success: function(data) {
                    alert(data.namer + '--' + data.song);
                }
            })
        };
        
        btn2.onclick = function() {
            jsonp({
                url: 'http://127.0.0.1:3000/test',
                data: {
                    namer: '周深',
                    song: '大魚'
                },
                success: function(data) {
                    alert(data.namer + '--' + data.song);
                }
            })
        };
    </script>
</body>

後台:

app.get('/test', function(req, res) {
    res.jsonp(req.query)
})

3.2 CORS 跨域資源共享

CORS 全稱為 Cross-origin Rescourse Sharing,即跨域資源共享,是W3C推出的一種新的機制。

它允許瀏覽器向跨域服務器發送Ajax請求,克服了Ajax 只能同源使用的限制

img

所以,當我們在使用CORS處理跨域請求時,瀏覽器判斷這是一個跨域請求,會自動幫我們處理好相應的跨域請求配置,添加一些附加頭部信息,而我們要做的僅僅是在服務器端判斷是否允許這個域訪問

img

1、簡單請求

簡單請求必須滿足三個請求:

  1. 請求方式為GETPOSTHEAD
  2. 數據類型Content-Type只能是application/x-www-form-urlencodedmultipart/form-datatext/plain
  3. 不使用自定義的請求頭

所以,如果是簡單請求,可以設置一個允許跨域請求的頭信息

在服務端設置:

app.post('/cache', function(req, res) {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080')
    res.send('你要一直走,直到燈火通明')
})

img

允許這個 http://localhost:8080 域請求,如果我們想要任何域能夠請求,只需要換成 * 即可

res.setHeader('Access-Control-Allow-Origin', '*')

現在任何所屬域的Ajax來請求這個服務器,都會被賦予訪問權限,都可以正常響應數據了

2、預請求

域請求是一種相對比較複雜的一些的請求,當出現以下條件時,就會被當做預請求

  1. 請求方式是 GETPOSTHEAD以外的方式,比如:PUTDELETE
  2. 使用 POST請求,但數據類型是 application/xml 或者 text/xml 的XML數據類型
  3. 使用自定義的請求頭信息

3、附帶憑證信息的請求

XMLHttpRequest 對象在發送請求的同時會發送憑證(Cookie 和 驗證信息),但是跨域請求並不會發送

所以想要傳遞Cookie給服務器,就要在請求頭裏面設置允許發送憑證信息。客户端和服務端都需要設置

感興趣的話可以繼續探索......

總結

  1. Ajax的同源策略就是説只有協議、主機號、端口的都一樣的域才能進行請求,而這樣做的目的就是為了安全,防止網站信息被竊取
  2. 解決跨域請求的方式有很多,JSONP 請求的是可執行腳本,適合用於請求我們自己的服務器
  3. CORS 是H5的一種新特性,基本支持所有的請求,也是我們常用的,缺點就是低版本的瀏覽器會有兼容性問題
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.