|
作者:高鵬 鏈接:tingandpeng.com/2016/09/05/前端跨域請(qǐng)求原理及實(shí)踐/ 一、 跨域請(qǐng)求的含義 瀏覽器的同源策略,出于防范跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對(duì)不同域的服務(wù)進(jìn)行跨站調(diào)用。 一般的,只要網(wǎng)站的 協(xié)議名protocol、 主機(jī)host、 端口號(hào)port 這三個(gè)中的任意一個(gè)不同,網(wǎng)站間的數(shù)據(jù)請(qǐng)求與傳輸便構(gòu)成了跨域調(diào)用。這也是我們下面實(shí)踐的理論基礎(chǔ)。我們利用 NodeJs 創(chuàng)建了兩個(gè)服務(wù)器,分別監(jiān)聽 3000、 3001 端口(下面簡稱 服務(wù)器3000 與 服務(wù)器3001 ),由于端口號(hào)不一樣,這兩個(gè)服務(wù)器以及服務(wù)器上頁面通信構(gòu)成了跨域請(qǐng)求。 在服務(wù)器3000 上有如下的頁面: 服務(wù)器3000 上的請(qǐng)求頁面中包含如下 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); } }); }); }); 服務(wù)器3000 對(duì)應(yīng)的處理函數(shù)為 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() }) 請(qǐng)求頁面返回結(jié)果: 此處數(shù)據(jù)處理成功。 由于數(shù)據(jù)請(qǐng)求一般都是由頁面發(fā)送數(shù)據(jù)字段,服務(wù)器根據(jù)這些字段作相應(yīng)的處理,如數(shù)據(jù)庫查詢,字符串操作等等。所以我們這里簡單的處理數(shù)據(jù)(在數(shù)據(jù)后面加上字符串‘server 3000 process’),并且返回給瀏覽器,表示數(shù)據(jù)經(jīng)過服務(wù)器端處理。 如果讓 服務(wù)器3000 上的頁面向 服務(wù)器 3001 發(fā)起請(qǐng)求會(huì)怎樣呢? 將請(qǐng)求頁面中的 ajax 請(qǐng)求路徑改為: $.ajax({ ... url: 'http://localhost:3001/ajax/deal', ... }); 服務(wù)器3001 對(duì)應(yīng)的處理函數(shù)與 服務(wù)器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() }) 結(jié)果如下: 結(jié)果證明了我們上面所說的端口號(hào)不同,發(fā)生了跨域請(qǐng)求的調(diào)用。 需要注意的是,服務(wù)器 3001 控制臺(tái)有輸出: server accept: chiaki 3001 這說明跨域請(qǐng)求并非是瀏覽器限制了發(fā)起跨站請(qǐng)求,而是請(qǐng)求可以正常發(fā)起,到達(dá)服務(wù)器端,但是服務(wù)器返回的結(jié)果會(huì)被瀏覽器攔截。 二、 利用 JSONP 實(shí)現(xiàn)跨域調(diào)用 說道跨域調(diào)用,可能大家首先想到的或者聽說過的就是 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 的一種使用模式,可以解決主流瀏覽器的跨域數(shù)據(jù)訪問問題。其原理是根據(jù) XmlHttpRequest 對(duì)象受到同源策略的影響,而 這樣從服務(wù)器返回的代碼就可以直接在這個(gè) script 標(biāo)簽中運(yùn)行了。下面我們自己實(shí)現(xiàn)一個(gè) JSONP: 服務(wù)器 3000請(qǐng)求頁面的 JavaScript 代碼中,只有回調(diào)函數(shù) jsonpCallback: function jsonpCallback(data) { console.log('jsonpCallback: '+data.name) } 服務(wù)器 3000請(qǐng)求頁面還包含一個(gè) script 標(biāo)簽: 服務(wù)器 3001上對(duì)應(yīng)的處理函數(shù): 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() }) 與上面一樣,我們?cè)谒@取的參數(shù)后面加上 “ – server 3001 jsonp process” 代表服務(wù)器對(duì)數(shù)據(jù)的操作。從代碼中我么可以看到,處理函數(shù)除了根據(jù)參數(shù)做相應(yīng)的處理,更多的也是進(jìn)行字符串的拼接。 最終的結(jié)果為: 2.4 JSONP 總結(jié) 至此,我們了解了 JSONP 的原理以及實(shí)現(xiàn)方式,它幫我們實(shí)現(xiàn)前端跨域請(qǐng)求,但是在實(shí)踐的過程中,我們還是可以發(fā)現(xiàn)它的不足: 只能使用 GET 方法發(fā)起請(qǐng)求,這是由于 script 標(biāo)簽自身的限制決定的。 不能很好的發(fā)現(xiàn)錯(cuò)誤,并進(jìn)行處理。與 Ajax 對(duì)比,由于不是通過 XmlHttpRequest 進(jìn)行傳輸,所以不能注冊(cè) success、 error 等事件監(jiān)聽函數(shù)。 三、 使用 CORS 實(shí)現(xiàn)跨域調(diào)用 3.1 什么是 CORS? Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術(shù)的規(guī)范,提供了 Web 服務(wù)從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現(xiàn)代版。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以讓網(wǎng)頁設(shè)計(jì)師用一般的 XMLHttpRequest,這種方式的錯(cuò)誤處理比 JSONP 要來的好。另一方面,JSONP 可以在不支持 CORS 的老舊瀏覽器上運(yùn)作。現(xiàn)代的瀏覽器都支持 CORS。 3.2 CORS 的實(shí)現(xiàn) 還是以 服務(wù)器 3000 上的請(qǐng)求頁面向 服務(wù)器 3001 發(fā)送請(qǐng)求為例。 服務(wù)器 3000 上的請(qǐng)求頁面 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); } }); }); }); 服務(wù)器 3001上對(duì)應(yīng)的處理函數(shù): 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() }) 在服務(wù)器中對(duì)返回信息的請(qǐng)求頭進(jìn)行了設(shè)置。 最終的結(jié)果為: 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 的對(duì)比 CORS 除了 GET 方法外,也支持其它的 HTTP 請(qǐng)求方法如 POST、 PUT 等。 CORS 可以使用 XmlHttpRequest 進(jìn)行傳輸,所以它的錯(cuò)誤處理方式比 JSONP 好。 JSONP 可以在不支持 CORS 的老舊瀏覽器上運(yùn)作。 四、 一些其它的跨域調(diào)用方式 4.1 window.name window對(duì)象有個(gè)name屬性,該屬性有個(gè)特征:即在一個(gè)窗口 (window) 的生命周期內(nèi),窗口載入的所有的頁面都是共享一個(gè) window.name 的,每個(gè)頁面對(duì) window.name 都有讀寫的權(quán)限,window.name 是持久存在一個(gè)窗口載入過的所有頁面中的,并不會(huì)因新頁面的載入而進(jìn)行重置。 4.2 window.postMessage() 這個(gè)方法是 HTML5 的一個(gè)新特性,可以用來向其他所有的 window 對(duì)象發(fā)送消息。需要注意的是我們必須要保證所有的腳本執(zhí)行完才發(fā)送 MessageEvent,如果在函數(shù)執(zhí)行的過程中調(diào)用了他,就會(huì)讓后面的函數(shù)超時(shí)無法執(zhí)行。 參考:https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage 關(guān)注「前端大全」 看更多精選前端技術(shù)文章 ↓↓↓ |
|
|