1. 程式人生 > >Jsonp解決ajax跨域問題

Jsonp解決ajax跨域問題

一、介紹

最近跨域問題比較多,而且自己剛好也看到這一塊,就總結了一下,關於JSONP的東西百度的話東西確實很多,很多人都是複製別人的,如此下去,其實找的資料就那麼幾份,關鍵是我還看不懂,可能是能力問題吧,自己經過很多嘗試,所以總結了一下,終究還是弄懂了皮毛。注意一點是,這裡是用Jsonp解決ajax的跨域問題,具體的實現其實不是ajax

1、同源策略

瀏覽器有一個很重要的概念——同源策略(Same-Origin Policy)。所謂同源是指,域名,協議,埠相同。不同源的客戶端指令碼(javascript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。

2、JSONP

JSONP(JSON with Padding)是JSON的一種”使用模式”,可用於解決主流瀏覽器的跨域資料訪問的問題。由於同源策略,一般來說位於 server1.example.com 的網頁無法與不是 server1.example.com的伺服器溝通,而 HTML 的script 元素是一個例外。利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。

二、實踐

1、模擬跨域請求

在本機弄兩個tomcat,埠分別為8080,8888,也就滿足了非同源的條件,那麼要是從一個埠傳送ajax去獲取另外一個埠的資料,那麼肯定會報跨域請求問題。

這裡寫圖片描述

這裡有兩個專案,分別是jsonp(8080),other(8888),在jsonp專案中index.jsp如下:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <head> <title>Insert title here</title> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript"> function jsonp_fun(){ $.ajax({ url:'http://localhost:8888/other/index.jsp', type:'post', dataType:'text', success:function(data){ console.log(data); } }); } </script> </head> <body> <input type="button" value="jsonp" onclick="jsonp_fun()"/> </body> </html>

other(8888)專案中index.jsp如下:// 因為jsp實際就是servlet,這裡就用jsp代替servlet演示。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
    other domain
</body>
</html>

其實中上面看無非就是jsonp頁面中點選按鈕ajax去獲取other頁面中的資料。

結果如下:chrome控制檯

這裡寫圖片描述

XMLHttpRequest cannot load http://localhost:8888/other/index.jsp. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.

以上提示就是指跨域問題,不能從8080這個域去訪問8888域的資源。

2、利用script標籤去訪問other域的js檔案

由於<script>標籤的src是支援跨域請求的。最常見的就是CDN服務的應用啦,比如我專案中,如果想用jquery,但是就沒有這個js檔案,去下載要找很久,而且版本還不知道下的對不對,那麼可以百度搜jquery cdn,我隨便找一個,比如bootstrap的cdn:http://www.bootcdn.cn/jquery/,有很多版本供你選擇,只要在專案中加上就行了,最大缺點的話就是你沒網的話,就引入不到啦。

  • 2.1 在other根路徑建立js/other.js檔案,內容如下:
alert("this is other(8888) js");
  • 2.2 在jsonp/index.jsp中,加入script標籤,引入other的js
<script type="text/javascript" src="http://localhost:8888/other/js/other.js"></script>

這裡寫圖片描述

  • 2.3 同樣的,直接引用,會立馬執行立馬的alert,那麼在other.js中寫函式,同樣jsonp/index.jsp中也能呼叫到,這點就不演示了,專案開發中大多都是這樣做的,頁面與js/css分離。

  • 2.4 另外說明一點,如果在other.js中有函式通過ajax呼叫8080中的東西,然後引入之後,呼叫這個函式,也是可以的,但是如果other.js中函式ajax呼叫8888的東西,引入之後,呼叫這個函式,同樣是跨域的。

3、script實現跨域請求

  • 3.1 簡單模擬伺服器返回資料

將jsonp/index.jsp改成如下:這裡注意引入的other.js的位置,是在函式getResult之後的,如果在它之前的話,會提示函式不存在。js載入順序是從上開始,在之前呼叫沒建立的,不能成功。注意這裡是指引入的js檔案,如果是同一個js檔案或者當前頁面的js中,先執行呼叫,然後再寫函式也是沒有問題的,但是如果先執行呼叫引入js檔案中的函式,然後再引入js檔案,就會提示函式不存在。

<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
    function jsonp_fun(){
        $.ajax({
            url:'http://localhost:8888/other/index.jsp',
            type:'post',
            dataType:'text',
            success:function(data){
                console.log(data);
            }
        });
    }

    function getResult(data){
        alert(data.result);
    }
</script>
<script type="text/javascript" src="http://localhost:8888/other/js/other.js"></script>

然後other.js

getResult({"result":"this is other domain's data"});

也就是在jsonp/index.jsp頁面寫好函式,然後引入其他域的js傳入引數去呼叫這個函式,這裡的引數你可以先看做是其他域伺服器的介面返回的資料。

重新整理頁面,效果當然是

彈出alert框,this is other domain's data
  • 3.2 模擬介面訪問
    看到這裡,你會不會還是想不懂,上面js弄啥的,傳個死的資料,有什麼實際意義嗎?,其實script的src不僅可以接js的地址,還可以接servlet的地址,也就是http介面地址,所以接下來,懶得寫servlet,這裡還是寫jsp當做介面,在other專案中新建other.jsp頁面,內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String params = request.getParameter("params");
    out.println("ajax cross success,the server receive params :"+params);
%>

內容很簡單,也就是接受一個params的引數,然後返回資料給呼叫者。

我們在jsonp/index.jsp中加上

<script type="text/javascript" src="http://localhost:8888/other/other.jsp?params=fromjsonp"></script>

看到這個地址,你是不是很熟悉,不熟悉的證明你用servlet用蠢了,jsp也是servlet,流程就是頁面一載入的時候,script標籤就會去傳送請求,然後返回資料。那麼我們重新整理頁面,看看效果。

這裡寫圖片描述

Uncaught SyntaxError: Unexpected identifier

報錯了,如上,然後程式碼有問題?No,點選錯誤,你會看到請求的東西也打印出來了,就是提示錯誤,表示這個東西瀏覽器不認識,其實是script不認識啦。

這裡寫圖片描述

還不明白,那麼你去頁面加上如下內容,你看報不報錯!!肯定報錯

<script type="text/javascript">
    ajax cross success,the server receive params : jsonp_param
</script>

那麼js不能解析,我們換一種思路,要是我們輸出的是JSON字串或者呼叫當前頁面函式的字串了,類似於3.1中返回的
getResult({“result”:”this is other domain’s data”});

所以改造一下,把other.jsp中的內容改成

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String params = request.getParameter("params");
    //out.println("ajax cross success,the server receive params :"+params);
    out.println("getResult({'result':'"+params+"'})");
%>

別忘了,之前jsonp/index.jsp中我們定義了,那麼加入引用之後,依然記得getResult函式與引入函式的先後順序問題。

<script type="text/javascript">
    function getResult(data){
        alert(data.result);
    }
</script>
<script type="text/javascript" src="http://localhost:8888/other/other.jsp?params=fromjsonp"></script>

重新整理頁面,發現大工告成。

這裡寫圖片描述

至此,大部分原理已經講完了,還有一個問題,這裡伺服器返回的是getResult(xxx),其中這裡的xxx可以當做是經過介面的很多處理,然後塞進去的值,但是這個getResult這個函式名,呼叫方與其他域伺服器這一方怎麼約定這個名字是一致的了,況且很多公司自己做服務的,別的公司的開發人員去呼叫,難道每個人都去那麼公司去約定呼叫函式的名字?怎麼可能,所以有人就想出來了一種解決方案,當然不是我~~,其實也很簡單啦,也就是把回撥的函式名字也一起傳過去不就行了,所以程式碼如下:

<script type="text/javascript" src="http://localhost:8888/other/other.jsp?params=fromjsonp&callback=getResult"></script>

other.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String params = request.getParameter("params");
    String callback = request.getParameter("callback");
    // 經過該介面一系列操作,然後得到data,將data返回給呼叫者
    String data = "{'result':'"+params+"'}";
    out.println(callback + "("+data+")");
%>

程式碼很簡單,也就是傳遞一個回撥函式的引數名,然後經過該介面一系列操作,將返回資料,塞到回撥函式裡面,呼叫端的函式就得到了該介面的資料,也就是類似於ajax中succsss:function(data),然後處理data一樣,這裡的success回撥函式,相當於上面的getResult函式。當然你也可以寫的優雅一點,比如:

    function CreateScript(src) {
        $("<script><//script>").attr("src", src).appendTo("body")
    }

    function jsonp_fun(){
         CreateScript("http://localhost:8888/other/other.jsp?params=fromjsonp&callback=getResult")
    }

4、Jquery的JSONP

至此跨域請求的原理已經講清楚了,但是仍然還有一個問題,總覺得這樣用有點怪是不是,如果用jquery的話,呼叫就很簡單了,其實jquery底層實現也是拼了一個script,然後指定src這種方式,跟上面講的一樣,只是jquery封裝了一下,顯得更加優雅,跟ajax呼叫方式差不多,所以容易記,程式碼如下:

<script type="text/javascript">
    function getResult(data){
        alert("through jsonp,receive data from other domain : "+data.result);
    }

    function jsonp_fun(){
        $.ajax({
            url:'http://localhost:8888/other/other.jsp',
            type:'post',
            data:{'params':'fromjsonp'},
            dataType: "jsonp",
            jsonp: "callback",//傳遞給請求處理程式或頁面的,用以獲得jsonp回撥函式名的引數名(一般預設為:callback)
            jsonpCallback:"getResult",//自定義的jsonp回撥函式名稱,預設為jQuery自動生成的隨機函式名,也可以不寫這個引數,jQuery會自動為你處理資料
            success: function(data){
            },
            error: function(){
                alert('fail');
            }
        });
    }
</script>
<body>
    <input type="button" value="jsonp" onclick="jsonp_fun()"/>
</body>

這裡的jsonCallback,回撥函式設定為getResult,那麼返回後會先呼叫getResult函式中的程式碼,再呼叫success函式中的程式碼,一般情況下,不用定義getResult函式,同樣jsonCallback不需要設定,那麼就只執行success中的程式碼,也就跟平時的ajax一樣用啦。

所以實際工作用法如下:

function jsonp_fun(){
        $.ajax({
            url:'http://localhost:8888/other/other.jsp',
            type:'post',
            data:{'params':'fromjsonp'},
            dataType: "jsonp",
            jsonp: "callback",//傳遞給請求處理程式或頁面的,用以獲得jsonp回撥函式名的引數名(一般預設為:callback)
            success: function(data){
                alert("through jsonp,receive data from other domain : "+data.result);
            },
            error: function(){
                alert('fail');
            }
        });
    }
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String params = request.getParameter("params");
    String callback = request.getParameter("callback");
    // 經過該介面一系列操作,然後得到data,將data返回給呼叫者
    String data = "{\"result\":\""+params+"\"}";
    out.println(callback + "("+data+")");
%> 

這裡沒有指定jsonpCallback,實際上jquery底層拼裝了一個函式名,當然生成函式規則就沒研究了。

這裡寫圖片描述

補充:

  • 1、ajax和jsonp這兩種技術在呼叫方式上“看起來”很像,目的也一樣,都是請求一個url,然後把伺服器返回的資料進行處理,因此jquery和ext等框架都把jsonp作為ajax的一種形式進行了封裝;

  • 2、但ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態新增<script>標籤來呼叫伺服器提供的js指令碼。

  • 3、所以說,其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的資料的獲取。

  • 4、還有就是,jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞資料,如果你願意,字串都行,只不過這樣不利於用jsonp提供公開服務。

另外補充最後一點:

Jsonp能解決的ajax跨域請求其實相當有限,推薦還是使用CROS,因為Jsonp的請求只能是get,雖然在上面演示中,我設定的type為post,但是實際上發的請求還是get,所以也就造成了萬一我的請求是post了,怎麼辦,要是跨域ajax檔案上傳了,怎麼辦,這點希望大家注意:

這裡寫圖片描述