做者:高鵬前端
連接:tingandpeng.com/2016/09/05/前端跨域請求原理及實踐/jquery
1、 跨域請求的含義web
瀏覽器的同源策略,出於防範跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不一樣域的服務進行跨站調用。ajax
通常的,只要網站的 協議名protocol、 主機host、 端口號port 這三個中的任意一個不一樣,網站間的數據請求與傳輸便構成了跨域調用。這也是咱們下面實踐的理論基礎。咱們利用 NodeJs 建立了兩個服務器,分別監聽 3000、 3001 端口(下面簡稱 服務器3000 與 服務器3001 ),因爲端口號不同,這兩個服務器以及服務器上頁面通訊構成了跨域請求。數據庫
在服務器3000 上有以下的頁面:json
服務器3000 上的請求頁面中包含以下 JavaScript 代碼:跨域
$(function() {瀏覽器
$("#submit").click(function() {服務器
var data = {app
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
這說明跨域請求並不是是瀏覽器限制了發起跨站請求,而是請求能夠正常發起,到達服務器端,可是服務器返回的結果會被瀏覽器攔截。
2、 利用 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 等事件監聽函數。
3、 使用 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、 一些其它的跨域調用方式
4.1 window.name
window對象有個name屬性,該屬性有個特徵:即在一個窗口 (window) 的生命週期內,窗口載入的全部的頁面都是共享一個 window.name 的,每一個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。
4.2 window.postMessage()
這個方法是 HTML5 的一個新特性,能夠用來向其餘全部的 window 對象發送消息。須要注意的是咱們必需要保證全部的腳本執行完才發送 MessageEvent,若是在函數執行的過程當中調用了他,就會讓後面的函數超時沒法執行。
參考:https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage