WithCoderWithCoderWithCoder

使用jsonp解决ajax跨域问题

    一、 同源策略

    在前端开发中,所有支持Javascript的浏览器都会使用“同源策略”这个安全策略。

    同源策略(Same origin policy),它是由Netscape提出的一个著名的安全策略,它是一种约定,是浏览器最核心也最基本的安全功能。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指“协议+域名+端口”三者相同
    同源策略是为了安全,确保一个应用中的资源只能被本应用的资源访问。

    而解决这种同源策略的方法称之为跨域,跨域的方法有很多种,本文简单介绍一下最经典的jsonp跨域。   

    二、JSON和JSONP

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式

    JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

    jsonp可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 mydomain1.com 的网页无法与不是 mydomain1.com的服务器沟通,而 HTML 的script 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

    三、模拟Ajax跨域

    仅看定义有点不太直接,我们通过简单的代码来进行模拟跨域(后台使用thinkphp框架)。本机添加两个域名mydomain1.com和mydomain2.com,指向同一个服务。新建一个index.html网页,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="refresh" content="86400">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JSONP</title>
    <script type="text/javascript" src="/static/plugins/jquery/jquery.min.js"></script>
<body>
<div>
    <button onclick="handleAjax()">Ajax跨域请求</button>
</div>
<script>
    // jquery模拟ajax跨域
    function handleAjax() {
        $.ajax({
            url: "http://mydomain2.com/index.php/Ext/Jsonp/ajaxapi/name/zhangsan",
            dataType: "json",
            success: function (d) {
                alert(d['res']);
            }
        });
    }
</script>
</body>
</html>

    PHP后台接口(通过mydomain2.com提供服务):

public function ajaxapi()
{
    // 请求参数
    $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil';
    // 返回结果
    $d['res'] = 'hello ' . $name;
    $this->ajaxReturn($d);
}

    如果ajax请求中的域名为mydomain1.com,点击按钮“Ajax跨域请求”,页面将成功弹出提示框,同域请求不再过多说明。

    而如果将请求域名更改为mydomain2.com,通过mydomain2.com访问index.html,点击按钮,控制台会提示跨域请求错误,截图如下:

    1-2010261505401E.png

    意思就是无法请求到 mydomain2.com 的资源,原因就是禁止了跨域请求资源。(当然,如果PHP后台添加header("Access-Control-Allow-Origin: *");,可以解决跨域问题,不过这样做不安全)。

    JSONP的原理:在页面js中创建一个回调函数,然后在远程服务上调用这个函数并且将服务端结果数据以JSON形式作为参数传递,完成回调。

    四、js实现跨域

    上面提到过,<script>标签的src是支持跨域请求的。最常见的就是CDN服务的应用。因此,根据JSONP的实现原理,在页面通过<script>标签的src属性,直接访问跨域接口。   

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="refresh" content="86400">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JSONP</title>
<body>
<div>
    <button onclick="addScriptTag()">js跨域请求</button>
</div>
<script>
    function callback(d) {
        alert(d['res']);
    }

    // 添加<script>标签,实现跨域
    function addScriptTag(src) {
        var script = document.createElement('script');
        script.setAttribute("type", "text/javascript");
        script.src = "http://mydomain2.com/index.php/Ext/Jsonp/jsonpapi.html?name=zhangsan&callback=callback";
        document.body.appendChild(script);
    }
</script>
</body>
</html>

    PHP后台接口修改如下:

public function jsonpapi()
{
    // 请求参数
    $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil';
    // 返回结果
    $d['res'] = 'hello ' . $name;

    $this->ajaxReturn($d, 'jsonp');
    // 或使用以下代码返回
    // if (!isset($_REQUEST ['callback'])) {
    //     $_REQUEST ['callback'] = 'function_callbak';
    // }
    // $jsoncallback = htmlspecialchars($_REQUEST ['callback']);
    // echo $jsoncallback . "(" . json_encode($d) . ")";
}

    刷新页面,点击按钮“js跨域请求”,跨域看到成功执行了跨域请求并执行了回调。

    以上代码很简单,就是传递一个回调函数的参数名,后台业务处理完成后,将返回数据传入回调函数里面,调用页面的函数得到了该接口返回的方法数据,使用Javascript解释器执行回调代码,也就是类似于ajax中succsss:function(data)。

    五、JQuery使用JSONP解决跨域问题

    jQuery 提供了方便使用 JSONP 的方式,它的底层实现也是拼了一个script,然后指定src这种方式,跟上面js的实现是一样,只是jquery封装了一下,显得更加优雅。虽然jQuery的JSONP使用类似 ajax 的请求,但是本质是不一样的,且只能使用 get 请求。

    将上面的index.html页面修改如下(添加ajax的JSONP请求,注意请求的配置参数):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta http-equiv="refresh" content="86400">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JSONP</title>
    <script type="text/javascript" src="/static/plugins/jquery/jquery.min.js"></script>
<body>
<div>
    <button onclick="handleAjax()">Ajax跨域请求</button>
    <button onclick="handleJsonp()">JSONP跨域请求</button>
</div>
<script>
    // jquery模拟ajax跨域(后台可以添加header("Access-Control-Allow-Origin: *");,解决跨域问题)
    function handleAjax() {
        $.ajax({
            url: "http://mydomain2.com/index.php/Ext/Jsonp/ajaxapi/name/zhangsan",
            dataType: "json",
            success: function (d) {
                alert(d['res']);
            }
        });
    }

    // jquery实现JSONP跨域请求
    function handleJsonp() {
        $.ajax({
            url: "http://mydomain2.com/index.php/Ext/Jsonp/jsonpapi/name/zhangsan",
            dataType: "jsonp",
            jsonpCallback: "callback",
            success: function (d) {
                alert("success执行:" + d['res']);
            }
        });
    }

    // JSONP回调方法
    function callback(d) {
        alert("回调执行:" + d['res']);
    }

</script>
</body>
</html>

     PHP后台接口修改如下:

public function ajaxapi()
{
    // 请求参数
    $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil';
    // 返回结果
    $d['res'] = 'hello ' . $name;

    $this->ajaxReturn($d);
}

public function jsonpapi()
{
    // 请求参数
    $name = isset($_REQUEST ['name']) ? $_REQUEST ['name'] : 'neil';
    // 返回结果
    $d['res'] = 'hello ' . $name;

    $this->ajaxReturn($d, 'jsonp');
    // 或使用以下代码返回
    // if (!isset($_REQUEST ['callback'])) {
    //     $_REQUEST ['callback'] = 'function_callbak';
    // }
    // $jsoncallback = htmlspecialchars($_REQUEST ['callback']);
    // echo $jsoncallback . "(" . json_encode($d) . ")";
}

    刷新页面,点击按钮“Ajax跨域请求”,页面仍会提示跨域。而点击按钮“JSONP跨域请求”,则会发现请求成功。

    不过我们可能注意到,页面弹出了2次提示框,分别是执行了callback和success方法。这是因为回调函数设置为callback,那么返回后会先调用callback函数中的代码,再调用success函数中的代码。一般情况下,不用定义callback函数,同样jsonpCallback也不需要设置,此时就只执行success中的代码,也就跟平时的ajax处理方式相同。

    

欢迎分享交流,转载请注明出处:WithCoder » 使用jsonp解决ajax跨域问题