使用JSONP實現跨域

什麼是跨域?javascript

簡單的來講,出於安全方面的考慮,頁面中的JavaScript沒法訪問其餘服務器上的數據,即「同源策略」。而跨域就是經過某些手段來繞過同源策略限制,實現不一樣服務器之間通訊的效果。css

什麼是JSONP?html

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,JSONP 是 JSON with padding(填充式 JSON 或參數式 JSON)的簡寫。JSONP(JSON with Padding)則是JSON 的一種「使用模式」,經過這種模式能夠實現數據的跨域獲取。JSONP 由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字通常是在請求中指定的。而數據就是傳入回調函數中的 JSON 數據。前端

JSONP跨域的基本原理java

在同源策略下,在某個服務器下的頁面是沒法獲取到該服務器之外的數據的,但img、iframe、script等標籤是個例外,這些標籤能夠經過src屬性請求到其餘服務器上的數據。利用script標籤的開放策略,咱們能夠實現跨域請求數據,固然,也須要服務端的配合。當咱們正常地請求一個JSON數據的時候,服務端返回的是一串JSON類型的數據,而咱們使用JSONP模式來請求數據的時候,服務端返回的是一段可執行的JavaScript代碼。 jquery

舉個例子,假如須要從服務器(http://www.a.com/user?id=123)獲取的數據以下:ajax

  1. {"id": 123, "name" : 張三, "age": 17}

那麼,使用JSONP方式請求(http://www.a.com/user?id=123?callback=foo)的數據將會是以下: json

  1. foo({"id": 123, "name" : 張三, "age": 17});

固然,若是服務端考慮得更加充分,返回的數據可能以下: 跨域

  1. try{foo({"id": 123, "name" : 張三, "age": 17});}catch(e){}

這時候咱們只要定義一個foo()函數,並動態地建立一個script標籤,使其的src屬性爲http://www.a.com/user?id=123?callback=foo: 瀏覽器

即可以使用foo函數來調用返回的數據了。 

 

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跨域

jQuery的ajax方法對JSONP式跨域進行了封裝,若是使用jQuery進行JSONP原理式的跨域HTTP請求,將會變得很是簡單:

<!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",

            async: false,

            url: "http://localhost:8546/Service.ashx",

            dataType: "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自動生成的函數名稱

 

必需要強調的是:

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

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

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

相關文章
相關標籤/搜索