前端跨域請求理及實踐

                                    

tingandpeng.com/2016/09/05/前端跨域請求原理及實踐/

一、 跨域請求的含義

瀏覽器的同源策略,出於防範跨站指令碼的攻擊,禁止客戶端指令碼(如 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’,

            data: data,

            url: ‘http://localhost:3000/ajax/deal’,

            dataType: ‘json’,

            cache: false,

            timeout: 5000,

            success: function(data) {

                console.log(data)

            },

            error: function(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 = {

        name: req.body.name   ‘ – server 3000 process’,

        id: req.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 = {

        name: req.body.name   ‘ – server 3001 process’,

        id: req.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’,

        data: data,

        dataType: ‘jsonp’,

        cache: false,

        timeout: 5000,

        // jsonp 欄位含義為伺服器通過什麼欄位獲取回撥函式的名稱

        jsonp: ‘callback’,

        // 宣告本地回撥函式的名稱,jquery 預設隨機生成一個函式名稱

        jsonpCallback: ‘jsonpCallback’,

        success: function(data) {

            console.log(“ajax success callback: ”   data.name)

        },

        error: function(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’,

            data: data,

            url: ‘http://localhost:3001/cors’,

            dataType: ‘json’,

            cache: false,

            timeout: 5000,

            success: function(data) {

                console.log(data)

            },

            error: function(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 = {

        name: req.body.name   ‘ – server 3001 cors process’,

        id: req.body.id   ‘ – server 3001 cors process’

    }

    console.log(data)

    res.send(data)

    res.end()

})

在伺服器中對返回資訊的請求頭進行了設定。

最終的結果為:

3.3 CORS 中屬性的分析

 

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.

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.

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 的對比

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

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

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

四、 一些其它的跨域呼叫方式

4.1 window.name

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

4.2 window.postMessage()

這個方法是 HTML5 的一個新特性,可以用來向其他所有的 window 物件傳送訊息。需要注意的是我們必須要保證所有的指令碼執行完才傳送 MessageEvent,如果在函式執行的過程中呼叫了他,就會讓後面的函式超時無法執行。

關聯文章