深入剖析jsonp跨域原理

在项目中遇到一个jsonp跨域的问题,于是仔细的研究了一番jsonp跨域的原理。搞明白了一些以前不是很懂的地方,比如:

1)jsonp跨域只能是get请求,而不能是post请求;

2)jsonp跨域的原理到底是什么;

3)除了jsonp跨域之外还有那些方法绕过“同源策略”,实现跨域访问;

4)jsonp和ajax,或者说jsonp和XMLHttpRequest是什么关系;

等等。

1.同源策略

说到跨域,首先要明白“同源策略”。同源是指:js脚本只能访问或者请求相同协议,相同domain(网址/ip),相同端口的页面。

我们知道,js脚本可以访问所在页面的所有元素。通过ajax技术,js也可以访问同一协议,同一个domain(ip),同一端口的服务器上的其他页面,请求到浏览器端之后,利用js就可以进行任意的访问。但是对于协议不同, 或者domain不同或者端口不同的服务器上的页面就无能为力了,完全不能进行请求。

下面在本地搭建两个tomcat,分别将端口设为8080,和8888,进行相关实验。显然他们的端口是不同的。演示如下:

http://localhost:8888/html4/ajax.html的代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<!doctype html>

<html>

<head>

    <meta charset="utf-8">

    <meta name="keywords" content="jsonp">

    <meta name="description" content="jsonp">

    <title>jsonp</title>

    <style type="text/css">

        *{margin:0;padding:0;}

        a{display:inline-block;margin:50px 50px;}

    </style>

</head>

<body>

    <a href="javascript:;" onclick="myAjax();">click me</a>

     

<script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>

<script type="text/javascript">

function myAjax(){

    var xmlhttp;

    if(window.XMLHttpRequest){

        xmlhttp = new XMLHttpRequest();

    }else{

        xmlhttp = ActionXObject("Microsoft.XMLHTTP");

    }

     

    xmlhttp.onreadystatechange = function(){

        if (xmlhttp.readyState==4 && xmlhttp.status==200){

            console.log(xmlhttp.responseText);         

        }

    }

    var url = "http://localhost:8080/minisns/json.jsp" + "?r=" + Math.random();

    xmlhttp.open("Get", url, true);

    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");

    xmlhttp.send();

}

</script>

</body>

</html>

这里为了结果不受其他js库的干扰,使用了原生的XMLHttpRequest来处理,结果如下:

我们看到8080端口的js的ajax请求无法访问8888端口的页面。原因是“同源策略不允许读取”。

既然普通的ajax不能访问,那么怎样才能访问呢?大家都知道,使用jsonp啊,那jsonp的原理是什么呢?他为什么能跨域呢?

2.jsonp跨域的原理

我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片,像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上连接,如下所示,所以它们是可以链接访问到不同源的资源的。

1)<script type="text/javascript" src="某个cdn地址" ></script>

2)<link type="text/css" rel="stylesheet" href="某个cdn地址" />

3)<img src="某个cdn地址" alt=""/>

而jsonp就是利用了<script>标签可以链接到不同源的js脚本,来到达跨域目的。当链接的资源到达浏览器时,浏览器会根据他们的类型来采取不同的处理方式,比如,如果是css文件,则会进行对页面 repaint,如果是img 则会将图片渲染出来,如果是script 脚本,则会进行执行,比如我们在页面引入了jquery库,为什么就可以使用 $ 了呢?就是因为 jquery 库被浏览器执行之后,会给全局对象window增加一个属性: $ ,所以我们才能使用 $ 来进行各种处理。(另外为什么要一般要加css放在头部,而js脚本放在body尾部呢,就是为了减少repaint的次数,另外因为js引擎是单线程执行,如果将js脚本放在头部,那么在js引擎在执行js代码时,会造成页面暂停。)

利用 页面上 script 标签可以跨域,并且其 src 指定的js脚本到达浏览器会执行的特性,我们可以进行跨域取得数据。我们用一个例子来说明:

8888端口的html4项目中的jsonp.html页面代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

<!doctype html>

<html>

<head>

    <meta charset="utf-8">

    <meta name="keywords" content="jsonp">

    <meta name="description" content="jsonp">

    <title>jsonp</title>

</head>

<body>

<script type="text/javascript" src="js/jquery-1.11.1.js"></script>

<script type="text/javascript">

var url = "http://localhost:8080/html5/jsonp_data.js";

// 创建script标签,设置其属性

var script = document.createElement('script');

script.setAttribute('src', url);

// 把script标签加入head,此时调用开始

document.getElementsByTagName('head')[0].appendChild(script);

function callbackFun(data)

{

    console.log(data.age);

    console.log(data.name);

}  

</script>

</body>

</html>

 其访问的8080端口的html5项目中的jsonp_data.js代码如下:

1

callbackFun({"age":100,"name":"yuanfang"})

 将两个tomcate启动,用浏览器访问8888端口的html4项目中的jsonp.html,结果如下:

上面我们看到,我们从8888 端口的页面通过 script 标签成功 的访问到了8080 端口下的jsonp_data.js中的数据。这就是 jsonp 的基本原理,利用script标签的特性,将数据使用json格式用一个函数包裹起来,然后在进行访问的页面中定义一个相同函数名的函数,因为 script 标签src引用的js脚本到达浏览器时会执行,而我们有定义了一个同名的函数,所以json格式的数据,就做完参数传递给了我们定义的同名函数了。这样就完成了跨域数据交换。jsonp的含义是:json with padding,而在json数据外包裹它的那个函数,就是所谓的 padding 啦^--^

明白了原理之后,我们再看一个更加实用的例子:

8080端口的html5项目中定义一个servlet:

复制代码

package com.tz.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;

@WebServlet("/JsonServlet")
public class JsonServlet extends HttpServlet 
{
    private static final long serialVersionUID = 4335775212856826743L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException 
    {
        String callbackfun = request.getParameter("mycallback");
        System.out.println(callbackfun);    // callbackFun
        response.setContentType("text/json;charset=utf-8");
        
        User user = new User();
        user.setName("yuanfang");
        user.setAge(100);
        Object obj = JSON.toJSON(user);
        
        System.out.println(user);            // com.tz.servlet.User@164ff87
        System.out.println(obj);            // {"age":100,"name":"yuanfang"}
        callbackfun += "(" + obj + ")";    
        System.out.println(callbackfun);    // callbackFun({"age":100,"name":"yuanfang"})
        
        response.getWriter().println(callbackfun);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException 
    {
        this.doPost(request, response);
    }

}

复制代码

 在8888端口的html4项目中的jsonp.html来如下的跨域访问他:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

<!doctype html>

<html>

<head>

    <meta charset="utf-8">

    <meta name="keywords" content="jsonp">

    <meta name="description" content="jsonp">

    <title>jsonp</title>

    <style type="text/css">

        *{margin:0;padding:0;}

        div{width:600px;height:100px;margin:20px auto;}

    </style>

</head>

<body>

    <div>

        <a href="javascript:;">jsonp测试</a>

    </div>

     

<script type="text/javascript" src="js/jquery-1.11.1.js"></script>

<script type="text/javascript">

function callbackFun(data)

{

    console.log(111);

    console.log(data.name);

    //data.age = 10000000;

    //alert(0000);

}

$(function(){

    $("a").on("click", function(){     

        $.ajax({

            type:"post",

            url:"http://localhost:8080/html5/JsonServlet",

            dataType:'jsonp',

            jsonp:'mycallback',

            jsonpCallback:'callbackFun',

            success:function(data) {

                console.log(2222);

                console.log(data.age);

            }

        });

    })

});

</script>

</body>

</html>

 结果如下:

我们看到,我们成功的跨域取到了servlet中的数据,而且在我们指定的回调函数jsonpCallback:'callbackFun' 和 sucess 指定的回调函数中都进行了执行。而且总是callbackFun先执行,如果我们打开注释://data.age = 10000000; //alert(0000);

就会发现:在callbackFun中对 data 进行修改之后,success指定的回调函数的结果也会发生变化,而且通过alert(0000),我们确定了如果alert(000)没有执行完,success指定的函数就不会开始执行,就是说两个回调函数是先后同步执行的。

结果如下:

3.jsonp 跨域与 ajax 

从上面的介绍和例子,我们知道了 jsonp 跨域的原理,是利用了script标签的特性来进行的,但是这和ajax有什么关系呢?显然script标签加载js脚本和ajax一点关系都没有,在没有ajax技术之前,script标签就存在了的。只不过是jquery的封装,使用了ajax来向服务器传递 jsonp 和 jsonpCallback 这两个参数而已。我们服务器端和客户端实现对参数 jsonp 和 jsonpCallback 的值,协调好,那么就没有必要使用ajax来传递着两个参数了,就像上面第二个例子那样,直接构造一个script标签就行了。不过实际上,我们还是会使用ajax的封装,因为它在调用完成之后,又将动态添加的script标签去掉了,我们看下相关的源码:

上面的代码先构造一个script标签,然后注册一个onload的回调,最后将构造好的script标签insert进去。insert完成之后,会触发onload回调,其中又将前面插入的script标签去掉了。其中的 代码 callback( 200, "success" ) 其实就是触发 ajax 的jsonp成功时的success回调函数,callback函数其实是一个 done 函数,其中包含了下面的代码:

因为传入的是 200 ,所以 isSuccess = true; 所以执行 "success"中的回调函数,response = ajaxHandleResponse(...) 就是我们处理服务器servelt返回的数据,我们可以调试:console.log(response.data.age); console.log(response.data.name); 看到结果。

3.jsonp 跨域与 get/post 

我们知道 script,link, img 等等标签引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get 的,那么为什么我们上面的代码中使用的 post 请求也成功了呢?这是因为当我们指定dataType:'jsonp',不论你指定:type:"post" 或者type:"get",其实质上进行的都是 get 请求!!!从两个方面可以证明这一点:

1)如果我们将JsonServlet中的 doGet()方法注释掉,那么上面的跨域访问就不能进行,或者在 doPost() 和 doGet() 方法中进行调试,都可以证明这一点;

2)我们看下firebug中的“网络”选项卡:

我们看到,即使我们指定 type:"post",当dataType:"jsonp" 时,进行的也是 GET请求,而不是post请求,也就是说jsonp时,type参数始终是"get",而不论我们指定他的值是什么,jquery在里面将它设定为了get. 我们甚至可以将 type 参数注释掉,都可以跨域成功:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

$(function(){

    $("a").on("click"function(){     

        $.ajax({

            //type:"post",

            url:"http://localhost:8080/html5/JsonServlet",

            dataType:'jsonp',

            jsonp:'mycallback',

            jsonpCallback:'callbackFun',

            success:function(data) {

                console.log(2222);

                console.log(data.age);

            }

        });

    })

});

所以jsonp跨域只能是get,jquery在封装jsonp跨域时,不论我们指定的是get还是post,他统一换成了get请求,估计这样可以减少错误吧。其对应的query源码如下所示:

复制代码

// Handle cache's special case and global
jQuery.ajaxPrefilter( "script", function( s ) {
    if ( s.cache === undefined ) {
        s.cache = false;
    }
    if ( s.crossDomain ) {
        s.type = "GET";
        s.global = false;
    }
});

复制代码

if( s.crossDomain){ s.type = "GET"; ...} 这里就是真相!!!!!!!!在ajax的过滤函数中,只要是跨域,jquery就将其type设置成"GET",真是那句话:在源码面前,一切了无秘密!jquery源码我自己很多地方读不懂,但是并不妨碍我们去读,去探索!

4.除了jsonp跨域方法之外的其他跨域方法

其实除了jsonp跨域之外,还有其他方法绕过同源策略,

1)因为同源策略是针对客户端的,在服务器端没有什么同源策略,是可以随便访问的,所以我们可以通过下面的方法绕过客户端的同源策略的限制:客户端先访问 同源的服务端代码,该同源的服务端代码,使用httpclient等方法,再去访问不同源的 服务端代码,然后将结果返回给客户端,这样就间接实现了跨域。相关例子,参见博文:http://www.cnblogs.com/digdeep/p/4198643.html

2)在服务端开启cors也可以支持浏览器的跨域访问。cors即:Cross-Origin Resource Sharing 跨域资源共享。jsonp和cors的区别是jsonp几乎所有浏览器都支持,但是只能是get,而cors有些老浏览器不支持,但是get/post都支持,cors的支持情况,可以参见下图(来自:http://caniuse.com/#search=cors)

cors实例:

项目html5中的Cors servlet:

复制代码

public class Cors extends HttpServlet 
{
    private static final long serialVersionUID = 1L;
       

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.getWriter().write("cors get");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.getWriter().write("cors post");
    }

}

复制代码

在html4项目中访问他:

复制代码

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="keywords" content="jsonp">
    <meta name="description" content="jsonp">
    <title>cors</title>
    <style type="text/css">
        *{margin:0;padding:0;}
        div{width:600px;height:100px;margin:20px auto;}
    </style>
</head>
<body>
    <div>
        <a href="javascript:;">cors测试</a>
    </div>
    
<script type="text/javascript" src="js/jquery-1.11.1.js"></script>
<script type="text/javascript">
$(function(){
    $("a").on("click", function(){        
        $.ajax({
            type:"post",
            url:"http://localhost:8080/html5/cors",
            success:function(data) {
                console.log(data);
                alert(data);
            }
        });
    })
});    
</script>
</body>
</html>

复制代码

访问结果如下:

5. 参数jsonp 和 jsonpCallback

jsonp指定使用哪个名字将回调函数传给服务端,也就是在服务端通过 request.getParameter(""); 的那个名字,而jsonpCallback就是request.getParamete("")取得的值,也就是回调函数的名称。其实这两个参数都可以不指定,只要我们是通过 success : 来指定回调函数的情况下,就可以省略这两个参数,jsnop如果不知道,默认是 "callback",jsnpCallback不指定,是jquery自动生成的一个函数名称,其对应源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

var oldCallbacks = [],

    rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings

jQuery.ajaxSetup({

    jsonp: "callback",

    jsonpCallback: function() {

        var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );

        this[ callback ] = true;

        return callback;

    }

});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/262583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

这是最简单的轮播图,图片自己加

代码&#xff1a; <!DOCTYPE html> <html> <head> <title>轮播图</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } .container { position: relative; overflow: hid…

Golang 的内存管理

文章目录 1.内存管理角色1.常见的内存分配方法线性分配器空闲链表分配器TCMalloc 2.Go 内存管理组件mspanmcache初始化替换微分配器 mcentralmheap 3.内存分配4.内存管理思想参考文献 1.内存管理角色 内存管理一般包含三个不同的组件&#xff0c;分别是用户程序&#xff08;Mu…

Nginx快速入门:负载均衡upstream配置详解(四)

0. 引言 我们在第二章的时候简单演示了关于nginx实现负载均衡的演示&#xff0c;而实际上nginx支持很多负载均衡算法&#xff0c;并且多节点的转发也有多种策略。今天我们继续深入学习这块。 1. 负载均衡的应用场景 所谓负载均衡&#xff0c;Load Balance &#xff0c;就是将…

Jmeter自定义用户变量模拟多用户

java1234,56a801e9c869452fa092c9657cfc2051 jack,b6e528cca41143dea9c2c3e9ca5d6390

Linux环境安装Hadoop

&#xff08;1&#xff09;下载Hadoop安装包并上传 下载Hadoop安装包到本地&#xff0c;并导入到Linux服务器的/opt/software路径下 &#xff08;2&#xff09;解压安装包 解压安装文件并放到/opt/module下面 [roothadoop100 ~]$ cd /opt/software [roothadoop100 software…

基于SpringBoot的教学管理app的开发-计算机毕业设计源码65449

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对教学管理等问题&#xff0c;对其进行研究分…

如何在本地安装Flask并将其web界面发布到公网上远程访问协同开发

目录 前言 1. 安装部署Flask 2. 安装Cpolar内网穿透 3. 配置Flask的web界面公网访问地址 4. 公网远程访问Flask的web界面 前言 本篇文章讲解如何在本地安装Flask&#xff0c;以及如何将其web界面发布到公网上并进行远程访问。 Flask是目前十分流行的web框架&#xff0c;…

电气 接近开关

npn&#xff1a;和负载&#xff08;控制器或者继电器&#xff09;共阳极&#xff0c;低电平响应 pnp&#xff1a;和负载共阴极&#xff0c;高电平响应

MyBatisX生成时的选项的含义

一般&#xff0c;annotation和template勾选MyBatis-Plus 3 options中各选项的作用 comment&#xff1a;实体类各属性的注释&#xff08;数据库中有的话&#xff09;以及生成TableId注解&#xff0c;同时会给serialVersionUID属性加上TableField(exist false) toString/hashCo…

OpenSergo使用详解

简介 OpenSergo是一个基于微服务治理的标准和生态&#xff0c;覆盖了服务元信息、流量治理、服务容错、数据库/缓存治理、服务注册发现、配置治理等十几个关键领域&#xff0c;覆盖了完整的微服务生命周期&#xff08;从开发态到测试态&#xff0c;到发布态&#xff0c;再到运…

不用翻箱倒柜找数据线,电脑投屏到另一台电脑,无线、远程投屏方法!

对于职场人士来说&#xff0c;电脑投屏电脑绝对是必不可少的工具&#xff0c;稳定高效的投屏开会是职场人渴望的技能&#xff0c;总比天天抱着笔记本走来走去&#xff0c;还要去翻箱倒柜的找数据线要强。如何实现电脑间的稳定投屏&#xff0c;AirDroid Cast提供了一个好思路。 …

ssm基于vue.js的连锁干洗店后台管理系统论文

摘 要 使用旧方法对连锁干洗店后台管理信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在连锁干洗店后台管理信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次…

最小操作次数问题

思路如下&#xff1a; 1.其他颜色变成红色球的情况:蓝色变红色需要2步 所以"2 绿色变红色需要1步 所以 绿色 2.其他颜色变成蓝色球的情况:绿色变蓝色需要2步 红色变蓝色需要1步 3.其他颜色变成绿色球的情况 红色变绿色需要2步 蓝色变绿色需要1步 代码如下&#xff1a…

免费分享一套Springboot+Vue前后端分离的个人博客系统,挺漂亮的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue前后端分离的个人博客系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue个人博客系统 毕业设计 Java毕业设计_哔哩哔哩_bilibili【免费】SpringbootVue个人博客系统 毕业设计 …

Unity DOTS物理引擎的核心分析与详解

最近DOTS发布了正式的版本,同时基于DOTS的理念实现了一套高性能的物理引擎&#xff0c;今天我们来给大家分享和介绍一下这个物理引擎的使用。 Unity.Physics的设计哲学 Unity.Physics是基于DOTS设计思想的一个高性能C#物理引擎的实现, 包含了物理刚体的迭代计算与碰撞检测等查…

什么专业适合学网络安全?

1.选什么专业 想当黑客要学什么专业&#xff1f;首先要明白一个点&#xff0c;当黑客你只需要会攻击手段就可以了&#xff0c;但绝不是只会使用一些工具而已&#xff0c;不然那就是“脚本小子”。 不严格来说&#xff0c;计算机所有的专业都可以当黑客&#xff0c;因为基础的东…

《Nature》预测 2024 科技大事:GPT-5预计明年发布等

《Nature》杂志近日盘点了 2024 年值得关注的科学事件&#xff0c;包括 GPT-5 与新一代 AlphaFold、超算 Jupiter、探索月球任务、生产「超级蚊子」、朝向星辰大海、试验下一代新冠疫苗、照亮暗物质、意识之辩第二回合、应对气候变化。 今年以来&#xff0c;以 ChatGPT 为代表…

【飞凌 OK113i-C 全志T113-i开发板】一些有用的常用的命令测试

一些有用的常用的命令测试 一、系统信息查询 可以查询板子的内核信息、CPU处理器信息、环境变量等 二、CPU频率 从上面的系统信息查询到&#xff0c;这是一颗具有两个ARMv7结构A7内核的处理器&#xff0c;主频最高1.2GHz 可以通过命令查看当前支持的频率以及目前所使用主频 …

fastapi 处理请求参数整理

1、路径参数 app.get("/getfilecontent/{id}/{pre}") def get_filecontent(id,pre):"""路径参数&#xff0c;测试接口&#xff0c;读取当前工作目录下的file.txt文件&#xff0c;返回txt中的内容:param id: The ID of the item to retrieve.:param p…

解决使用傅里叶变换开源库fftw分析音频频谱结果与matlab或audacity不一致的问题

找的一些demo输出结果与实际结果相差巨大&#xff0c;修复后效果如下&#xff1a; 采用一个采样率48000&#xff0c;精度16bit&#xff0c;单通道的46Hz,振幅为32767的正弦波测试&#xff08;理论上应该得输出一个一模一样的正弦波&#xff09;。输出如下图&#xff0c;可以看…