前段請求跨域的原理及實踐

一、 跨域請求的含義


瀏覽器的同源策略,出於防範跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不同域的服務進行跨站調用。


一般的,只要網站的 協議名protocol、 主機host、 端口號port 這三個中的任意一個不同,網站間的數據請求與傳輸便構成了跨域調用。這也是我們下面實踐的理論基礎。我們利用 NodeJs 創建了兩個服務器,分別監聽 3000、 3001 端口(下面簡稱 服務器3000 與 服務器3001 ),由於端口號不一樣,這兩個服務器以及服務器上頁面通信構成了跨域請求。


在服務器3000 上有如下的頁面:



服務器3000 上的請求頁面中包含如下 JavaScript 代碼:


$(function() {

    $("#submit").click(function() {

        var data = {

            name$("#name").val(),

            id$("#id").val()

        };

        $.ajax({

            type'POST',

            datadata,

            url'http://localhost:3000/ajax/deal',

            dataType'json',

            cachefalse,

            timeout5000,

            successfunction(data) {

                console.log(data)

            },

            errorfunction(jqXHR, textStatus, errorThrown) {

                console.log('error ' + textStatus + ' ' + errorThrown);

            }

        });

    });

});


服務器3000 對應的處理函數爲


pp.post('/ajax/deal', function(req, res) {

    console.log("server accept: ", req.body.name, req.body.id)

    var data = {

        namereq.body.name + ' - server 3000 process',

        idreq.body.id + ' - server 3000 process'

    }

    res.send(data)

    res.end()

})


請求頁面返回結果:



此處數據處理成功。


由於數據請求一般都是由頁面發送數據字段,服務器根據這些字段作相應的處理,如數據庫查詢,字符串操作等等。所以我們這裏簡單的處理數據(在數據後面加上字符串‘server 3000 process’),並且返回給瀏覽器,表示數據經過服務器端處理。

如果讓 服務器3000 上的頁面向 服務器 3001 發起請求會怎樣呢?


將請求頁面中的 ajax 請求路徑改爲:


$.ajax({

    ...

    url'http://localhost:3001/ajax/deal',

    ...

});


服務器3001 對應的處理函數與 服務器3000 類似:


app.post('/ajax/deal', function(req, res) {

    console.log("server accept: ", req.body.name, req.body.id)

    var data = {

        namereq.body.name + ' - server 3001 process',

        idreq.body.id + ' - server 3001 process'

    }

    res.send(data)

    res.end()

})


結果如下:



結果證明了我們上面所說的端口號不同,發生了跨域請求的調用。


需要注意的是,服務器 3001 控制檯有輸出:


server accept:  chiaki 3001


這說明跨域請求並非是瀏覽器限制了發起跨站請求,而是請求可以正常發起,到達服務器端,但是服務器返回的結果會被瀏覽器攔截。


二、 利用 JSONP 實現跨域調用


說道跨域調用,可能大家首先想到的或者聽說過的就是 JSONP 了。


2.1 什麼是JSONP


JSONP (JSON with Padding or JSON-P) is a JSON extension used by web developers to overcome the cross-domain restrictions imposed by browsers’ same-origin policy that limits access to resources retrieved from origins other than the one the page was served by. In layman’s terms, one website cannot just simply access the data from another website.


It was developed because handling a browsers’ same origin policy can be difficult, so using JSONP abstracts the difficulties and makes it easier.


JSON stands for “JavaScript Object Notation”, a format by which object fields are represented as key-value pairs which is used to represent data.


JSONP 是 JSON 的一種使用模式,可以解決主流瀏覽器的跨域數據訪問問題。其原理是根據 XmlHttpRequest 對象受到同源策略的影響,而 <script> 標籤元素卻不受同源策略影響,可以加載跨域服務器上的腳本,網頁可以從其他來源動態產生 JSON 資料。用 JSONP 獲取的不是 JSON 數據,而是可以直接運行的 JavaScript 語句。


2.2 使用 jQuery 集成的 $.ajax 實現 JSONP 跨域調用


我們先從簡單的實現開始,利用 jQuery 中的 $.ajax 來實現上訴的跨域調用。


依然是上面的例子,我們將 服務器 3000 上的請求頁面的 JavaScript 代碼改爲:


// 回調函數

function jsonpCallback(data) {

    console.log("jsonpCallback: " + data.name)

}

$("#submit").click(function() {

    var data = {

        name$("#name").val(),

        id$("#id").val()

    };

    $.ajax({

        url'http://localhost:3001/ajax/deal',

        datadata,

        dataType'jsonp',

        cachefalse,

        timeout5000,

        // jsonp 字段含義爲服務器通過什麼字段獲取回調函數的名稱

        jsonp'callback',

        // 聲明本地回調函數的名稱,jquery 默認隨機生成一個函數名稱

        jsonpCallback'jsonpCallback',

        successfunction(data) {

            console.log("ajax success callback: " + data.name)

        },

        errorfunction(jqXHR, textStatus, errorThrown) {

            console.log(textStatus + ' ' + errorThrown);

        }

    });

});


服務器 3001 上對應的處理函數爲:


app.get('/ajax/deal', function(req, res) {

    console.log("server accept: ", req.query.name, req.query.id)

    var data = "{" + "name:'" + req.query.name + " - server 3001 process'," + "id:'" + req.query.id + " - server 3001 process'" +"}"

    var callback = req.query.callback

    var jsonp = callback + '(' + data + ')'

    console.log(jsonp)

    res.send(jsonp)

    res.end()

})


這裏一定要注意 data 中字符串拼接,不能直接將 JSON 格式的 data 直接傳給回調函數,否則會發生編譯錯誤: parsererror Error: jsonpCallback was not called。


其實腦海裏應該有一個概念:利用 JSONP 格式返回的值一段要立即執行的 JavaScript 代碼,所以不會像 ajax 的 XmlHttpRequest 那樣可以監聽不同事件對數據進行不同處理。


處理結果如下所示:



2.3 使用 <script> 標籤原生實現 JSONP


經過上面的事件,你是不是覺得 JSONP 的實現和 Ajax 大同小異?


其實,由於實現的原理不同,由 JSONP 實現的跨域調用不是通過 XmlHttpRequset 對象,而是通過 script 標籤,所以在實現原理上,JSONP 和 Ajax 已經一點關係都沒有了。看上去形式相似只是由於 jQuery 對 JSONP 做了封裝和轉換。


比如在上面的例子中,我們假設要傳輸的數據 data 格式如下:


{

    name"chiaki",

    id": "3001"

}


那麼數據是如何傳輸的呢?HTTP 請求頭的第一行如下:


GET /ajax/deal?callback=jsonpCallback&name=chiaki&id=3001&_=1473164876032 HTTP/1.1


可見,即使形式上是用 POST 傳輸一個 JSON 格式的數據,其實發送請求時還是轉換成 GET 請求。


其實如果理解 JSONP 的原理的話就不難理解爲什麼只能使用 GET 請求方法了。由於是通過 script 標籤進行請求,所以上述傳輸過程根本上是以下的形式:


<script src = 'http://localhost:3001/ajax/deal?callback=jsonpCallback&name=chiaki&id=3001&_=1473164876032'></script>


這樣從服務器返回的代碼就可以直接在這個 script 標籤中運行了。下面我們自己實現一個 JSONP:


服務器 3000請求頁面的 JavaScript 代碼中,只有回調函數 jsonpCallback:


function jsonpCallback(data) {

    console.log("jsonpCallback: "+data.name)

}


服務器 3000請求頁面還包含一個 script 標籤:


<script src = 'http://localhost:3001/jsonServerResponse?jsonp=jsonpCallback'></script>


服務器 3001上對應的處理函數:


app.get('/jsonServerResponse', function(req, res) {

    var cb = req.query.jsonp

    console.log(cb)

    var data = 'var data = {' + 'name: $("#name").val() + " - server 3001 jsonp process",' + 'id: $("#id").val() + " - server 3001 jsonp process"' + '};'

    var debug = 'console.log(data);'

    var callback = '$("#submit").click(function() {' + data + cb + '(data);' + debug + '});'

    res.send(callback)

    res.end()

})


與上面一樣,我們在所獲取的參數後面加上 “ – server 3001 jsonp process” 代表服務器對數據的操作。從代碼中我麼可以看到,處理函數除了根據參數做相應的處理,更多的也是進行字符串的拼接。


最終的結果爲:



2.4 JSONP 總結


至此,我們瞭解了 JSONP 的原理以及實現方式,它幫我們實現前端跨域請求,但是在實踐的過程中,我們還是可以發現它的不足:


只能使用 GET 方法發起請求,這是由於 script 標籤自身的限制決定的。

不能很好的發現錯誤,並進行處理。與 Ajax 對比,由於不是通過 XmlHttpRequest 進行傳輸,所以不能註冊 success、 error 等事件監聽函數。


三、 使用 CORS 實現跨域調用


3.1 什麼是 CORS?


Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現代版。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以讓網頁設計師用一般的 XMLHttpRequest,這種方式的錯誤處理比 JSONP 要來的好。另一方面,JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。現代的瀏覽器都支持 CORS。


3.2 CORS 的實現


還是以 服務器 3000 上的請求頁面向 服務器 3001 發送請求爲例。


服務器 3000 上的請求頁面 JavaScript 不變,如下:


$(function() {

    $("#submit").click(function() {

        var data = {

            name$("#name").val(),

            id$("#id").val()

        };

        $.ajax({

            type'POST',

            datadata,

            url'http://localhost:3001/cors',

            dataType'json',

            cachefalse,

            timeout5000,

            successfunction(data) {

                console.log(data)

            },

            errorfunction(jqXHR, textStatus, errorThrown) {

                console.log('error ' + textStatus + ' ' + errorThrown);

            }

        });

    });

});


服務器 3001上對應的處理函數:


app.post('/cors', function(req, res) {

    res.header("Access-Control-Allow-Origin", "*");

    res.header("Access-Control-Allow-Headers", "X-Requested-With");

    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

    res.header("X-Powered-By", ' 3.2.1')

    res.header("Content-Type", "application/json;charset=utf-8");

    var data = {

        namereq.body.name + ' - server 3001 cors process',

        idreq.body.id + ' - server 3001 cors process'

    }

    console.log(data)

    res.send(data)

    res.end()

})


在服務器中對返回信息的請求頭進行了設置。


最終的結果爲:


3.3 CORS 中屬性的分析


  1. Access-Control-Allow-Origin

    The origin parameter specifies a URI that may access the resource. The browser must enforce this. For requests without credentials, the server may specify “*” as a wildcard, thereby allowing any origin to access the resource.

  2. Access-Control-Allow-Methods

    Specifies the method or methods allowed when accessing the resource. This is used in response to a preflight request. The conditions under which a request is preflighted are discussed above.

  3. Access-Control-Allow-Headers

    Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.


3.4 CORS 與 JSONP 的對比


  1. CORS 除了 GET 方法外,也支持其它的 HTTP 請求方法如 POST、 PUT 等。

  2. CORS 可以使用 XmlHttpRequest 進行傳輸,所以它的錯誤處理方式比 JSONP 好。

  3. JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。


四、 一些其它的跨域調用方式


4.1 window.name


window對象有個name屬性,該屬性有個特徵:即在一個窗口 (window) 的生命週期內,窗口載入的所有的頁面都是共享一個 window.name 的,每個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的所有頁面中的,並不會因新頁面的載入而進行重置。


4.2 window.postMessage()


這個方法是 HTML5 的一個新特性,可以用來向其他所有的 window 對象發送消息。需要注意的是我們必須要保證所有的腳本執行完才發送 MessageEvent,如果在函數執行的過程中調用了他,就會讓後面的函數超時無法執行。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章