1. 程式人生 > >JSONP跨域請求原理及優缺點

JSONP跨域請求原理及優缺點

三、使用jquery的jsonp如何發起跨域請求及其原理

先看下準備環境:兩個埠不一樣,構成跨域請求的條件。

獲取資料:獲取資料的埠為9090

請求資料:請求資料的埠為8080

1、先看下直接發起ajax請求會怎麼樣

下面是發起請求端的程式碼:

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>
        $(document).ready(function () {
            
            $("#btn").click(function () {
                $.ajax({
                    url: 'http://localhost:9090/student',
                    type: 'GET',
                    success: function (data) {
                        $(text).val(data);
                    }
                });

            });
            
        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>
</body>
</html>

請求的結果如下圖:可以看到跨域請求因為瀏覽器的同源策略被攔截了。

 2、接下來看如何發起跨域請求

    解決跨域請求的方式有很多,這裡只說一下jquery的jsop方式及其原理。

    首先我們需要明白,在頁面上直接發起一個跨域的ajax請求是不可以的,但是,在頁面上引入不同域上的js指令碼卻是可以的,就像你可以在自己的頁面上使用<img src=""> 標籤來隨意顯示某個域上的圖片一樣。

    比如我在8080埠的頁面上請求一個9090埠的圖片:可以看到直接通過src跨域請求是可以的。

 3、那麼看下如何使用<script src="">來完成一個跨域請求:

  當點選"跨域獲取資料"的按鈕時,新增一個<script>標籤,用於發起跨域請求;注意看請求地址後面帶了一個callback=showData的引數;

  showData即是回撥函式名稱,傳到後臺,用於包裹資料。資料返回到前端後,就是showData(result)的形式,因為是script指令碼,所以自動呼叫showData函式,而result就是showData的引數。

  至此,我們算是跨域把資料請求回來了,但是比較麻煩,需要自己寫指令碼發起請求,然後寫個回撥函式處理資料,不是很方便。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>
        //回撥函式
        function showData (result) {
            var data = JSON.stringify(result); //json物件轉成字串
            $("#text").val(data);
        }

        $(document).ready(function () {

            $("#btn").click(function () {
                //向頭部輸入一個指令碼,該指令碼發起一個跨域請求
                $("head").append("<script src='http://localhost:9090/student?callback=showData'><\/script>");
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>
</body>
</html>

服務端:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    //資料
    List<Student> studentList = getStudentList();


    JSONArray jsonArray = JSONArray.fromObject(studentList);
    String result = jsonArray.toString();

    //前端傳過來的回撥函式名稱
    String callback = request.getParameter("callback");
    //用回撥函式名稱包裹返回資料,這樣,返回資料就作為回撥函式的引數傳回去了
    result = callback + "(" + result + ")";

    response.getWriter().write(result);
}

結果:

 4、再來看jquery的jsonp方式跨域請求:

    服務端程式碼不變,js程式碼如下:最簡單的方式,只需配置一個dataType:'jsonp',就可以發起一個跨域請求。jsonp指定伺服器返回的資料型別為jsonp格式,可以看發起的請求路徑,自動帶了一個callback=xxx,xxx是jquery隨機生成的一個回撥函式名稱。

這裡的success就跟上面的showData一樣,如果有success函式則預設success()作為回撥函式。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        $(document).ready(function () {

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp", //指定伺服器返回的資料型別
                    success: function (data) {
                        var result = JSON.stringify(data); //json物件轉成字串
                        $("#text").val(result);
                    }
                });

            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>

效果:

再看看如何指定特定的回撥函式:第30行程式碼

  回撥函式你可以寫到<script>下(預設屬於window物件),或者指明寫到window物件裡,看jquery原始碼,可以看到jsonp呼叫回撥函式時,是呼叫的window.callback。

  然後看呼叫結果,發現,請求時帶的引數是:callback=showData;呼叫回撥函式的時候,先呼叫了指定的showData,然後再呼叫了success。所以,success是返回成功後必定會呼叫的函式,就看你怎麼寫了。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        function showData (data) {
            console.info("呼叫showData");

            var result = JSON.stringify(data);
            $("#text").val(result);
        }

        $(document).ready(function () {

//            window.showData = function  (data) {
//                console.info("呼叫showData");
//
//                var result = JSON.stringify(data);
//                $("#text").val(result);
//            }

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp",  //指定伺服器返回的資料型別
                    jsonpCallback: "showData",  //指定回撥函式名稱
                    success: function (data) {
                        console.info("呼叫success");
                    }
                });
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>

效果圖:

再看看如何改變callback這個名稱:第23行程式碼

  指定callback這個名稱後,後臺也需要跟著更改。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        function showData (data) {
            console.info("呼叫showData");

            var result = JSON.stringify(data);
            $("#text").val(result);
        }

        $(document).ready(function () {

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "GET",
                    dataType: "jsonp",  //指定伺服器返回的資料型別
                    jsonp: "theFunction",   //指定引數名稱
                    jsonpCallback: "showData",  //指定回撥函式名稱
                    success: function (data) {
                        console.info("呼叫success");
                    }
                });
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>

</body>
</html>

後臺程式碼:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    //資料
    List<Student> studentList = getStudentList();


    JSONArray jsonArray = JSONArray.fromObject(studentList);
    String result = jsonArray.toString();

    //前端傳過來的回撥函式名稱
    String callback = request.getParameter("theFunction");
    //用回撥函式名稱包裹返回資料,這樣,返回資料就作為回撥函式的引數傳回去了
    result = callback + "(" + result + ")";

    response.getWriter().write(result);
}

效果圖:

最後看看jsonp是否支援POST方式:ajax請求指定POST方式

  可以看到,jsonp方式不支援POST方式跨域請求,就算指定成POST方式,會自動轉為GET方式;而後端如果設定成POST方式了,那就請求不了了。

  jsonp的實現方式其實就是<script>指令碼請求地址的方式一樣,只是ajax的jsonp對其做了封裝,所以可想而知,jsonp是不支援POST方式的。

<%@ page pageEncoding="utf-8" contentType="text/html;charset=UTF-8"  language="java" %>
<html>
<head>
    <title>跨域測試</title>
    <script src="js/jquery-1.7.2.js"></script>
    <script>

        $(document).ready(function () {

            $("#btn").click(function () {

                $.ajax({
                    url: "http://localhost:9090/student",
                    type: "POST",   //post請求方式
                    dataType: "jsonp",
                    jsonp: "callback",
                    success: function (data) {
                        var result = JSON.stringify(data);
                        $("#text").val(result);
                    }
                });
            });

        });
    </script>
</head>
<body>
    <input id="btn" type="button" value="跨域獲取資料" />
    <textarea id="text" style="width: 400px; height: 100px;"></textarea>
</body>
</html>

效果圖:

 再補充一點,回到第一條:CORS頭缺少“Access-Control-Allow-Origin”。

  有時候你會發現其它都沒問題,出現這個錯誤:這個錯誤代表服務端拒絕跨域訪問。如果出現這個錯誤,就需要在服務端設定允許跨域請求。

  response.setHeader("Access-Control-Allow-Origin", "*"); 設定允許任何域名跨域訪問

 設定可以跨域訪問:第6行程式碼或第8行程式碼,設定其中一個即可。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    // * 表示允許任何域名跨域訪問
    response.setHeader("Access-Control-Allow-Origin", "*");
    // 指定特定域名可以訪問
    response.setHeader("Access-Control-Allow-Origin", "http:localhost:8080/");

    //資料
    List<Student> studentList = getStudentList();

    JSONArray jsonArray = JSONArray.fromObject(studentList);
    String result = jsonArray.toString();

    //前端傳過來的回撥函式名稱
    String callback = request.getParameter("callback");
    //用回撥函式名稱包裹返回資料,這樣,返回資料就作為回撥函式的引數傳回去了
    result = callback + "(" + result + ")";

    response.getWriter().write(result);
}

四、JSONP的優缺點?

  • 優點

    它不像XMLHttpRequest物件實現的Ajax請求那樣受到同源策略的限制;它的相容性更好,在更加古老的瀏覽器中都可以執行,不需要XMLHttpRequest或ActiveX的支援;並且在請求完畢後可以通過呼叫callback的方式回傳結果。

  • 缺點

    它只支援GET請求而不支援POST等其它型別的HTTP請求;它只支援跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript呼叫的問題。

五、總結

    jQuery ajax方式以jsonp型別發起跨域請求,其原理跟<script>指令碼請求一樣,因此使用jsonp時也只能使用GET方式發起跨域請求。跨域請求需要服務端配合,設定callback,才能完成跨域請求。