淺談JSONP跨域請求

第一次聽說JSONP跨域請求還是去年剛入職實習的時候,也借那個機會在網上好好了解了下用法,但是對於其如何實現還是不太明白。昨天一同事問我當時對JSONP的使用情況,突然發現一年多沒用,對於這個跨域請求技術又忘的差不多了,剛好不知道這個星期該寫篇什麼方面的文章,那就趁這個機會再把JSONP琢磨琢磨順便把過程記錄下來,免得下次不記得了又得到網上到處找資源查閱。


OK,進入正題!!!!

先看JSONP定義

JSONP(JSON with Padding)是JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。由於同源策略,一般來說位於 server1.example.com 的網頁無法與不是 server1.example.com的服務器溝通,而 HTML 的<script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。
JSONP它是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
——來自百度

同源策略

同源策略是一種約定,也是瀏覽器本身最核心最基本的一個安全功能。
所謂同源是指協議、域名、端口相同,也就是說同源策略不允許一個站點的某個文檔或腳本加載請求另一個站點的文檔或腳本,具體看下如下實驗:

  1. 我新建一個站點作爲本地站點,端口號爲8080,如下:
  2. 再建一個站點作爲遠程站點,端口號爲8085,如下:
  3. 我在本地站點中添加兩個按鈕,一個請求遠程站點文檔,一個請求本地站點文檔,我們來看看效果如何
    請求代碼如下:

    1. //請求遠程站點文檔
    2. function remoteBtnClick() {
    3. $.get("http://localhost:8085/Remote/jslib/jquery-1.7.2.js",
    4. function(data){
    5. console.log(data);
    6. }
    7. );
    8. }
    9. //請求本地站點文檔
    10. function localBtnClick() {
    11. $.get("http://localhost:8080/Local/jslib/jquery-1.7.2.js",
    12. function(data){
    13. console.log(data);
    14. }
    15. );
    16. }

    請求響應結果如下:

    正如前面說的,根據同源策略瀏覽器不允許本地站點直接通過Http請求讀取另外一個遠程站點的資源信息。

看到這,相信大家很快會想到一種情況———引入外部js類庫<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0"></script>比如這個引入百度地圖JS類庫的代碼,很明顯,這個地圖JS類庫和我們自己的站點明顯不是在同一個域內,對於這種情況我也寫一個測試程序驗證一下。
我在本地站點8080的頁面中添加這樣一行代碼<script type="text/javascript" src="http://localhost:8085/Remote/jslib/remote.js"></script>以希望在本地頁面加載的時候同時加載遠程站點8085裏的remote.js文件,remote.js文件中的內容如下:

運行代碼之後的效果圖如下:
很顯然,通過這種方式是完全可以讀取遠程站點的文檔信息的,也就是說像這種src屬性的加載方式是不受同源策略所約束可以訪問任何站點的文檔信息,另外像大家用的很多的圖片加載標籤,也經常通過其src屬性加載網絡圖片。

實現基本的跨域請求

看了上面的例子和JSONP的定義相信大家已經明白了JSONP跨域請求的基本原理,利用script的這種加載方式我們就可以實現跨域請求。

我在本地站點中動態構造一個script標籤,然後將其src的url指向遠程站點的文檔,最後將這個script標籤添加到頁面dom中,先寫個例子看這種方式能不能行得通。

  1. //遠程站點8085上remote.js中的內容依然和上面一樣,不做任何改變
  2. function remoteBtnClick() {
  3. $("<script><//script>").attr("src", "http://localhost:8085/Remote/jslib/remote.js").appendTo("body")
  4. }

運行之後的結果和上面直接通過<script type="text/javascript" src="http://localhost:8085/Remote/jslib/remote.js"></script>加載的效果是一樣,這裏就不繼續貼圖,因爲原理是一樣,很顯然結果必然相同,而且也沒必要動態寫這麼麻煩,直接加載豈不是更直接。

接着我們來另外一個情況,既然JSONP定義中說了JSONP拿到的是JSON數據,那我們將遠程站點請求文件remote.js換成一個JSON數據文件remote.json({"data":"來自遠程站點的數據","time":"2014-12-20"})再試試。

  1. function remoteBtnClick() {
  2. $("<script><//script>").attr("src", "http://localhost:8085/Remote/remote.json").appendTo("body")
  3. }

圖1:

圖2:

看圖1中的請求響應結果,很明顯,這個json數據已經從遠程站點請求成功並且拿到了本地站點下。通過圖2我們發現這裏有個javascript的語法錯誤,因爲我們是通過javascript標籤的方式加載的,而這個標籤是將文檔加載完成後會立即把其當做js執行,而這個json數據很明顯不是一個合法的js語句。既然這樣,那我們就想辦法讓這個json數據變成一個合法的js語句,最簡單的方法就是將這個json數據當做一個函數的參數給塞進去,例如:callbackHandler({"data":"來自遠程站點的數據","time":"2014-12-20"}),如果本地站點中有一個callbackHandler函數,那麼遠程站點返回的這個數據就是一個合法的js函數,很顯然這個時候單純的通過js客戶端來驗證這個例子是無法實現的,因爲在遠程站點8085中無論是json文件還是js文件中都無法直接構造callbackHandler({"data":"來自遠程站點的數據","time":"2014-12-20"})這麼一段代碼(語法不正確)給本地站點8080去遠程調用。

  1. function callbackHandler(json) {
  2. console.log(json)
  3. }

這個時候就需要遠程站點的服務端進行配合,由於遠程站點後端服務不知道本地客戶端的回調函數名是callbackHandler,所以,我們需要在遠程調用的時候告訴服務端本地的回調函數名是callbackHandler,此時的本地站點8080上的請求方式則換這樣:

  1. function remoteBtnClick() {
  2. $("<script><//script>").attr("src", "http://localhost:8085/Remote/JSONPServlet?callback=callbackHandler").appendTo("body")
  3. }
  4. function callbackHandler(data) {
  5. console.log(data);
  6. }

遠程站點8085上服務端代碼如下:

  1. public void doPost(HttpServletRequest request, HttpServletResponse response)
  2. throws ServletException, IOException {
  3. String callback = request.getParameter("callback");
  4. response.setContentType("text/html");
  5. PrintWriter out = response.getWriter();
  6. out.println(callback + "({'data':'The data from remote','time':'2014-12-20'})");
  7. out.flush();
  8. out.close();
  9. }

服務端通過參數解析知道本地站點客戶端的回調函數名是callbackHandler,遠程服務端構造一段callbackHandler({"data":"The data from remote","time":"2014-12-20"})返回即可,這樣在script標籤加載完成後會直接將取得的json數據當做參數傳入該回調函數中執行,這樣整個跨域請求(請求服務端)就完成了。JSONP的跨域請求差不多就這樣實現了,但是
運行效果如下:

運行結果正如我們所想,通過script方式加載遠程服務返回javascript tags可以順利實現跨域訪問,這裏我也繼續試驗下直接通過ajax方式訪問遠程後端服務,將remoteBtnClick()實現改爲如下方式:

  1. function remoteBtnClick() {
  2. $.get("http://localhost:8085/Remote/JSONPServlet?callback=callbackHandler",
  3. function(data){
  4. console.log(data);
  5. }
  6. );
  7. }

請求結果如下:

顯然,請求的結果和前面的請求遠程站點客戶端文檔信息是一樣,因爲同源策略而無法訪問。


OK,JSONP的實現方式和相關驗證基本上就愛完成了,現在也知道了JSONP的實現原理和實現方式,但是上面這種實現方式有點麻煩,既要自己添加script標籤,同時還要自己定義一個回調函數,感覺略顯麻煩,其實jQuery中已經直接提供類似的JSONP請求方式,我們只需要按照其定義好調用方式即可進行Http的跨域請求。現在我將remoteBtnClick()實現方式修改爲如下,遠程服務端代碼不修改:

  1. //請求
  2. function remoteBtnClick() {
  3. $.ajax({
  4. url: 'http://localhost:8085/Remote/JSONPServlet',
  5. dataType: "jsonp",
  6. jsonp: "callback",
  7. jsonpCallback: "callbackHandler",
  8. success: function (data) {
  9. console.log(data);
  10. console.log("success");
  11. }
  12. });
  13. }
  14. //客戶端回調
  15. function callbackHandler(json) {
  16. console.log(json);
  17. console.log("callbackHandler");
  18. }

請求結果如下:

可以看到,現在直接通過ajax請求遠程站點服務也成功實現了跨域請求,這個是jQuery自己已經幫我們封裝好的功能。對於ajax請求中的幾個參數我簡單說描述下作用:
1. dataType:’jsonp’,這個是代表當前Http請求爲jsonp的請求方式;
2. jsonp:’callback’,這個代表的是遠程服務端接收客戶端回調函數名的參數名,即:String callback = request.getParameter("callback") 這個參數,ajax請求中jsonp參數的默認值就是callback,這個也可以自己隨便更換;
3. jsonpCallback:’callbackHandler’,這個代表遠程服務調用結束後的本地回調函數名,比如上面的代碼中的那個客戶端回調函數名,這個jsonpCallback的參數值也是可以自己隨便定義的,也可以不給這個jsonpCallback參數,其實jQuery會自動爲我們生成一個函數和函數名,從上面的結果圖中我們可以看到,遠程服務調用成功後,既執行了SUCCESS這個回調函數,也執行我們自己定義的callbackHandler這個回調函數,所以我們完全可以使用jQuery給我們生成的回調函數,在調用結束後在SUCCESS回調中做相應的處理即可,如下是不加該參數的調用方式:

  1. $.ajax({
  2. url: 'http://localhost:8085/Remote/JSONPServlet',
  3. dataType: "jsonp",
  4. jsonp: "callback",
  5. success: function (data) {
  6. console.log(data);
  7. }
  8. });

上面結果圖中的圈中部分就是jQuery爲我們自動生成的回調函數名。

OK,JSONP的實現方式及實現原理基本上介紹演示完了,至於jQuery中對於JSONP的實現封裝方式等我有時間研究下了再繼續吧~~~~~~~~~~


write by laohu

2015年3月20日

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