在進行AJAX的時候會常常產生這樣一個報錯:javascript
看紅字,這是瀏覽器的同源策略,使跨域進行的AJAX無效。注意,不是不發送AJAX請求(其實就是HTTP請求),而是請求了,也返回了,但瀏覽器‘咔擦’一聲,下面沒有了。對比下fiddler和瀏覽器抓的包的異同:
fiddler:html
chrome:java
簡而言之,瀏覽器這邊就是頭(response header)給看,身體(response body)不給看。ajax
什麼是同源策略?爲何會有同源策略?這一點在吳翰清老師著的《白帽子講Web安全》一書中由闡述,這裏就不贅述了。下面要作的,就是使用JSONP讓上面的報錯消失,按正確的流程進行下去。chrome
首先介紹下我這裏的環境,兩個Web服務器,Tomcat監聽8081,Node監聽3000,Tomcat這邊實現一個處理AJAX的JSP文件,很簡單,返回一個JSON
json
<%@ page contentType="application/json; charset=utf-8" %> {"status": true}
Tomcat的頁面對這個URL發出AJAX請求,並打印出了返回值跨域
但Node的頁面發出AJAX請求,則像上面那樣報錯了,由於AJAX有同源策略保護。怎麼繞過這個保護呢?平時咱們頁面引入的CSS、JS多是從其餘的服務器好比靜態服務器、CDN獲取內容,都在不一樣的域,可知頁面內的標籤引入JS是沒有同源策略一說的,並且也是進行request和處理response,因而咱們把這個AJAX請求改成以下代碼:瀏覽器
var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp'; document.body.insertBefore(script, document.body.lastChild);
但仍是殘忍的報錯了安全
由於返回的JSON({"status": true})成爲了一個獨立的js片斷,而這個片斷明顯是不符合語法的,若是返回的是符合語法規範的處理JSON的js片斷而不單單是JSON就行了。好比咱們將服務器端的代碼改爲這樣:服務器
<%@ page contentType="application/javascript; charset=utf-8" %> console.log({"status": true});
再在Node的頁面進行AJAX
目的是達到了,但問題是,這個AJAX的servlet不只返回了數據,還返回了行爲,難道我要把處理DOM的js寫在這裏面嗎?頁面重構了又跑到這裏來修改?問題太美不敢想,因此請求成功的方法必須寫在頁面的js裏面,好比這樣
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp'; document.body.insertBefore(script, document.body.lastChild);
而服務器返回的js片斷直接調用這個function就好了,這個就叫回調函數了
<%@ page contentType="application/javascript; charset=utf-8" %> callback({"status": true});
能夠看到,這個方案比以前好多了,servlet和請求頁面的耦合度低了不少,但沒徹底解決,好比callback這個回調函數的名字,若是把這個名字放在請求的parameter中,好比這樣
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp?cb=callback'; document.body.insertBefore(script, document.body.lastChild);
服務器對這個parameter進行處理
<%@ page contentType="application/javascript; charset=utf-8" %>
<%= request.getParameter("cb") %>({"status": true});
優化一下,對沒有cb參數的請求僅返回JSON
<% String callback = request.getParameter("cb"); if(null == callback) { response.setContentType("application/json; charset=utf-8"); %> {"status": true} <% }else { response.setContentType("application/javascript; charset=utf-8"); %> <%= callback %>({"status": true}) <% } %>
那麼整個JSONP的功能就實現了。但還有一點瑕疵,代碼執行完html中留下了一個script標籤,強迫症能忍?處女座能忍?
解決方法:可使用jQuery的方法,jQuery會清除掉留下的script標籤。
$.ajax({ url: 'http://localhost:8081/test/true.jsp', dataType: "jsonp", jsonp: "cb", success: function (data) { console.log(data) } });
也能夠本身實現一個,我拋個磚,在js加載完成後刪除節點。
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp?cb=callback'; document.body.insertBefore(script, document.body.lastChild); script.onload = function(){ this.parentNode.removeChild(this); };
至此,不知道有人發現沒有,JSONP這種方式有一個致命的缺陷:就是因爲它是經過引入script節點實現的,因此只支持GET方法。若是你任性,你無理取鬧,你必定要用post跨域,那麼只能考慮使用CORS了。
JSONP的東西就到此結束了,其實作完才發現,實際上這是個很簡單的概念,取了個比較唬人的名字而已。