跨域解決方案二:使用JSONP實現跨域


跨域的實現方式有多種,除了 上篇文章 提到的CORS外,常見的還有JSONP、HTML5、Flash、iframe、xhr2等。

這篇文章對JSONP的跨域原理進行了探索,並將我的心得記錄在這里和大家分享。

JSONP跨域原理探秘

我們知道,使用 XMLHTTPRequest 對象發送HTTP請求時,會遇到 同源策略 問題,域不同請求會被瀏覽器攔截。

那么是否有方法能繞過 XMLHTTPRequest 對象進行HTTP跨域請求呢?

換句話說,不使用 XMLHTTPRequest 對象是否可以發送跨域HTTP請求呢?

細心的你可能會發現,像諸如:

<script type="text/javascript" src="http://www.a.com/scripts/1.js"></script>

<img src="http://www.b.com/images/1.jpg" />

<link rel="stylesheet" href="http://www.c.com/assets/css/1.css" />

這種標簽是不會遇到"跨域"問題的,嚴格意義上講,這不是跨域,跨域是指在腳本代碼中向非同源域發送HTTP請求,這只是跨站資源請求。

那么,我們是否可以利用跨站資源請求這一方式來實現跨域HTTP請求呢?

以<script></script>標簽為例進行探索,先看一段代碼:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>jsonp demo</title>
    <!-- JavaScript片斷1 -->
    <script type="text/javascript">
        function handler(data) {
            alert(data);
            // our code here...
        }
    </script>

    <!-- JavaScript片斷2 -->
    <script type="text/javascript">
        handler('success');
    </script>
</head>
<body>
    A JSONP demo.
</body>
</html>

這段代碼中,有2個JavaScript片斷,第1個片斷中定義了一個處理函數handler(),這個處理函數比較簡單,沒有對數據做任何處理,只是把它alert出來;第2個片斷調用了它,運行這個頁面瀏覽器會彈出"success"。

我們假設第2個JavaScript片斷存儲在別的地方,然后我們使用<script src="" />的方式把它引入進來,像這樣:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>jsonp demo</title>
    <!-- JavaScript片斷1 -->
    <script type="text/javascript">
        function handler(data) {
            alert(data);
            // our code here...
        }
    </script>

    <!-- JavaScript片斷2 -->
    <script type="text/javascript" src="http://service.a.com/script/1.js"></script>
</head>
<body>
    A JSONP demo.
</body>
</html>

service.a.com/script/1.js:

handler('success');

這種方法和把JavaScript代碼直接寫在頁面是等效的,但是,我們由此可以聯想到什么?

我們是否可以事先在本頁面定義處理程序,服務端返回JS腳本,腳本的內容就是對處理程序的回調,服務返回的數據通過參數的形式傳回:

handler('服務返回的數據');

然后通過動態向當前頁面head節點添加<script src="服務地址"></script>節點的方式來“偽造”HTTP請求?

於是,可以編寫這樣一個簡單的測試用例:

先寫服務端,非常簡單的一個服務,返回字符串"Hello World",一般處理程序Service.ashx:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace JSONPDemo.Service
{
    /// <summary>
    /// Service2 的摘要說明
    /// </summary>
    public class Service2 : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            context.Response.Write("handler('Hello World');");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

再寫客戶端,一個簡單的靜態Web頁,index.html:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script type="text/javascript">
        // 跨域發送HTTP請求,從服務端獲取字符串"Hello World" 
        function getHello() {
            var script = document.createElement('script');
            script.setAttribute('src', 'http://localhost:8546/Service.ashx');
            document.querySelector("head").appendChild(script);
        }
        // 處理函數
        function handler(data) {
            alert(data);
            // our code here...
        }
    </script>
</head>
<body>
    <input type="button" value="發送跨域HTTP請求,獲取Hello World" onclick="getHello()" />
</body>
</html>

測試成功!

在這個測試例子中,我們使用一般處理程序編寫了一個簡單的返回Hello World的服務,然后使用動態創建<script></script>節點的方式實現了跨域HTTP請求。

由於<script>、<img>標簽資源請求是異步的,所以我們就實現了一個跨域的異步HTTP請求。

但是這么做是不夠的,一個頁面可能會有多個HTTP請求,而上面這個示例的處理程序只有一個——handler。

不同的請求應該由不同的處理程序來處理,對上面的代碼稍做修改,只需要給<script>標簽的src屬性中的URL添加一個參數來指定回調函數的名稱就可以了:

服務端:

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";

            // 前端指定的回調函數名稱
            var callbackFuncName = context.Request.QueryString["callback"];
            var responseData = "Hello World";
            // 回調腳本,形如:handler('responseData');
            var scriptContent = string.Format("{0}('{1}');", callbackFuncName, responseData);
            context.Response.Write(scriptContent);
        }

Web客戶端:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>jsonp demo</title>
    <script type="text/javascript">
        // 跨域發送HTTP請求,從服務端獲取字符串"Hello World" 
        function getHello() {
            var script = document.createElement('script');
            script.setAttribute('src', 'http://localhost:8546/Service.ashx?callback=handler');//callback指定回調函數名稱
            document.querySelector("head").appendChild(script);
        }
        // 處理函數
        function handler(data) {
            alert(data);
            // our code here...
        }
    </script>

</head>
<body>
    <input type="button" value="發送跨域HTTP請求,獲取Hello World" onclick="getHello()" />
</body>
</html>

使用jQuery的JSONP跨域

在上面的章節我們探及了JSONP跨域的原理。幸運的是,我們並不需要每次都寫這么多代碼來完成一次跨域請求。

jQuery的ajax方法對JSONP式跨域請求進行了封裝,如果使用jQuery進行JSONP式跨域請求,代碼將會變得非常簡單:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>jQuery jsonp demo</title>
    <script src="jquery-1.11.0.min.js"></script>
	<script type="text/javascript">
        $.ajax({
            type: "get",
            url: "http://localhost:8546/Service.ashx",
            dataType: "jsonp",//一定要指明為jsonp
            success: function (data) {
                alert(data);
            },
            error: function () {
                alert('fail');
            }
        });
    </script>
</head>
<body>
    使用jQuery一切將會變得非常簡單。
</body>
</html>

只需要將dataType設置為"jsonp"就可以進行跨域請求了,一切就像發送非跨域請求那樣簡單。

jQuery為我們封裝好了回調函數,一般情況下不需要我們單獨去寫,如果你不想在success中處理,想單獨寫處理函數,那么可以通過設置這2個參數來實現:

  • jsonp: "callback",//傳遞給服務端的回調函數參數名,如果不設置此項,則默認是"callback"
  • jsonpCallback: "handler",//傳遞給服務端的回調函數名稱,如果不設置此項,則默認是形如"jQuery111007837897759742043_1460657212499"的由jQuery自動生成的函數名稱,會自動在$.ajax方法的success中完成調用。

 

必須要強調的是:

1.JSONP雖然看起來很像一般的ajax請求,但其原理不同,JSONP是對文章第一小節原理的封裝,是通過<script>標簽的動態加載來實現的跨域請求,而一般的ajax請求是通過XMLHttpRequest對象進行;

2.JSONP不是一種標准協議,其安全性和穩定性都不如 W3C 推薦的 CORS

3.JSONP不支持POST請求,即使把請求類型設置為post,其本質上仍然是一個get請求。

 demo下載(1507)

JavaScript中的跨域解決方案匯總:

跨域解決方案一:使用CORS實現跨域

跨域解決方案二:使用JSONP實現跨域




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM