一次关于JSONP的小实验与总结

前言:

      今天,无心间看到本身某个文件夹下有个JSONP的东西。慢慢回忆起,这个东西是以前想写的一个demo,也不知道是多久之前了,可是不知道怎么的,给忘那边了。那么,就趁这个机会把它完成吧,其实也说不上是一个demo,就是一个小实验,虽然,网上也已经有不少关于JSONP的文章和例子了,可是有些东西看看很简单,不亲自试一下总以为不踏实。我今天为何要实验,一方面也是常常在网上看到有些网站须要跨域得到数据,可是目前本身作的项目中又没有相关需求,因而很好奇,因而就有了这篇文章,因而......那就开始此次练习吧。javascript

一 什么是JSONP

   JSONP全称:JSON with Padding php

   看到名字,好像说,JSONP是JSON的什么?或许有人会问,什么是JSON呢?若是有同窗还不清楚JSON,能够先去了解下JSON,而后再继续本文的阅读或许会更好。简单的说,JSON是一种数据交换格式,在咱们经过ajax技术获取数据的时候,能够以XML或者JSON这样的格式进行传递。ajax虽然好用,可是也有遇到困难的时候,好比你须要跨域获取数据。这个时候,普通的ajax获取方式就不太容易了,这时候,JSONP就能够帮忙了。这里再补充下前面提到跨域问题,跨域其实简单的说就是,好比你本身写了一个网站把它部署到域名是www.a.com的服务器上,而后你能够毫无压力的使用ajax请求www.a.com/users.json  的数据。 可是,当要你经过普通ajax方式请求www.b.com域名下的www.b.com/users.json的数据时,就没那么容易了,在后面的小实验中,能够看到这一状况。既然使用普通的ajax技术没法作到,那么JSONP又是如何作到的呢?html

二 JSONP的基本原理

   JSONP能够实现跨域,这要归功于强大的<script></script>元素标签。除了咱们会在它中间写js代码外,也常常会在网站中经过它的src属性引入外部js文件,关键就在此,咱们的引入的js文件也能够不是同一个域下的。那么咱们也就能够将原来须要获取的JSON数据写到js文件中去,而后再获取。不过,不幸的事情终究发生了,当咱们把一段JSON格式的数据,例如:java

1 {"id" : "1","name" : "小王"}

写入js文件,而后经过<script>元素引入后,却报错了。缘由是<script>标签元素仍是很老实的,由于它就是负责执行js的,因此你那个JSON格式的数据它也会坚决果断的看成js代码去执行,而那个数据根本不符合js语法,因而就很悲剧的出错了。但这个出错,一样却带给了咱们答案,不是吗?既然不符合js语法不行,咱们搞个符合的不就能够了。这里一种经常使用的办法就是返回一个函数callback({"id" : "1","name" : "小王"}); 的执行语句就能够了。这里的callback命名不是必须的,你能够换任何喜欢的名字。这里只是强调这是个回调函数才这么写。回调函数确实强大啊,要使得这里能够执行该函数,那么这个函数必须在开始就已经被咱们提早定义了。咱们在开始就定义好:jquery

function callback(data){ alert(data.name); }

其实这个不难理解,普通的函数执行或许你们都明白,在<script>标签中间先定义上面的函数,可是该函数并不会运行,由于你没执行调用,当你接着在代码中写上
callback({"id" : "1","name" : "小王"});就顺利的执行了。而JSONP所作的就是这个事情,只不过调用的语句从远程服务器传来,动态加入到你的页面中去执行而已。到这里只剩下最后一步了,就是告诉服务器端返回哪一个名称的函数执行,这个也好办,将函数名以一个查询参数传递到后台告诉它名字就行了,相似:ajax

http://www.b.com/getUsers.json?callback=getUsersjson

而后在服务器端处理,得到参数callback的值,而后将数据填充到getUser(data);的函数参数中去,这里的data。返回前台页面后,即可以执行并得到data数据了。到此,也终于明白了JSON with Padding中的Padding(填充)了。关于JSONP的基础理论部分就结束了,剩下的内容就剩下实验部分了。vim

 

三 JSONP小实验

  •   实验环境:windows操做系统。
  •   开发工具:NotePad++。
  •   开发语言:Node.js。
  •   前台使用插件:jQuery。

 开始了,这里选择Node.js,没其它缘由,我只是顺手抓到它了,你固然也能够用asp.net,java servlet,php,ruby,Golang等等等你喜欢的去实验。由于只实验JSONP,没多少东西,因此Node.js中也没有使用第三方的框架(不事后来有点后悔了,多写了好多......)。windows

首先须要模拟两个域,由于我在windows下,因此能够修改host文件,添加跨域

两个域名映射到本机回送地址127.0.0.1。而后开始写代码:

建立两个Node.js的应用,一个是appA.js,一个appB.js。首先,咱们尝试经过普通ajax获取同域的数据:

appB.js代码:

 

View Code
 1 var http = require('http'),  2       url = require('url'),  3       fs  = require('fs'),  4       path = require('path');  5      
 6 
 7 function getFile(localPath,mimeType,res){  8     fs.readFile(localPath,function(err,contents){  9         if(!err){ 10             res.writeHead(200,{ 11                 'Content-Type' : mimeType, 12                 'Content-length' : contents.length 13             
14  }); 15  res.end(contents); 16         }else{ 17             res.writeHead(500); 18  res.end(); 19  } 20     
21  }); 22 } 23 http.createServer(function(req,res){ 24     var urlPath = url.parse(req.url).pathname; 25     var fileName = path.basename(req.url) || 'index.html', 26          suffix  = path.extname(fileName).substring(1), 27          dir = path.dirname(req.url).substring(1), 28          localPath = __dirname + '\\'; 29     
30 
31     if(suffix === 'js'){ 32         localPath += (dir ? dir + '\\' : '') + fileName; 33         path.exists(localPath,function(exists){ 34             if(exists){ 35                 getFile(localPath,'js',res); 36             }else{ 37                 res.writeHead(404); 38  res.end(); 39  } 40             
41  }); 42         
43     }else{ 44         if(urlPath === '/index'){ 45         res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); 46         var html = '<!DOCTYPE html>'
47                     +'<head>'
48 +'<script type="text/javascript" src="jquery.js"></script>'
49                     
50                     +'<script type="text/javascript" src="http://www.a.com:8088/index?callback=getFollowers"></script>'
51                     +'<script>'
52                     +'$(function(){'
53                     +  ''
54                     +  '$("#getFo").click(function(){'
55                     +'    $.ajax({'
56                     +            'url:"http://www.b.com:9099/followers.json",'
57                     +       'type:"get",'
58                     +       'success:function(json){'
59                     +       '    alert(json.users[0].name);'
60                     +       '}'
61                     +        '});'
62                     +     ''
63                     +    '});'
64                     +'});'
65                     +'</script>'
66                     +'</head>'
67                     +'<body>'
68                     +'<h1>hello i am server b </h1>'
69                     +'<input id="getFo" type="button" value="获取个人粉丝"/>'
70                     +'</body>'
71                     +'</html>'; 72  res.write(html); 73  res.end(); 74     }else if(urlPath === '/followers.json'){ 75         res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'}); 76         var followers = { 77                                 "users" : [ 78                                     {"id" : "1","name" : "小王"}, 79                                     {"id" : "2","name" : "小李"} 80  ] 81  }; 82         var fjson = JSON.stringify(followers); 83  res.end(fjson); 84     }else{ 85         res.writeHead(404,{'Content-Type':'text/html;charset=utf-8'}); 86         res.end('page not found'); 87  } 88     
89 } 90     
91     
92 }).listen(9099); 93 console.log('Listening app B at 9099...');

 

以上截图是ajax请求数据部分。咱们打开浏览器,输入地址后,以下:

这里有个按钮获取个人粉丝,ajax就从url:"http://www.b.com:9099/followers.json该源得到数据,这个数据在代码中,咱们也能够找到,就是

 当点击获取后,以下:

 成功,没问题,咱们再复制一份同样的代码,另存为appA.js,而后修改listen端口:

修改appB.js中ajax请求的URL为http://www.a.com:8088/followers.json ,如今是appB服务器自己是http://www.b.com:9099/index 而去请求www.a.com下的数据===》启动它

而后点击获取粉丝按钮会发现:

 真的没有取到数据。。。。。。

再试试JSONP的方式,咱们修改appB.js以下:

View Code
 1 var http = require('http'),  2       url = require('url'),  3       fs  = require('fs'),  4       path = require('path');  5      
 6 
 7 function getFile(localPath,mimeType,res){  8     fs.readFile(localPath,function(err,contents){  9         if(!err){ 10             res.writeHead(200,{ 11                 'Content-Type' : mimeType, 12                 'Content-length' : contents.length 13             
14  }); 15  res.end(contents); 16         }else{ 17             res.writeHead(500); 18  res.end(); 19  } 20     
21  }); 22 } 23 http.createServer(function(req,res){ 24     var urlPath = url.parse(req.url).pathname; 25     var fileName = path.basename(req.url) || 'index.html', 26          suffix  = path.extname(fileName).substring(1), 27          dir = path.dirname(req.url).substring(1), 28          localPath = __dirname + '\\'; 29     
30 
31     if(suffix === 'js'){ 32         localPath += (dir ? dir + '\\' : '') + fileName; 33         path.exists(localPath,function(exists){ 34             if(exists){ 35                 getFile(localPath,'js',res); 36             }else{ 37                 res.writeHead(404); 38  res.end(); 39  } 40             
41  }); 42         
43     }else{ 44         if(urlPath === '/index'){ 45         res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); 46         var html = '<!DOCTYPE html>'
47                     +'<head>'
48                     +'<script type="text/javascript">var getFollowers= function(data){alert(decodeURIComponent(data.users[0].name));};</script>'
49                     +'<script type="text/javascript" src="jquery.js"></script>'
50                     
51                     +'<script type="text/javascript" src="http://www.a.com:8088/index?callback=getFollowers"></script>'
52                     +'<script>'
53                     +'$(function(){'
54                     +  ''
55                     +  '$("#getFo").click(function(){'
56                     +'    $.ajax({'
57                     +            'url:"http://www.a.com:8088/followers.json",'
58                     +       'type:"get",'
59                     +       'success:function(json){'
60                     +       '    alert(json.users[0].name);'
61                     +       '}'
62                     +        '});'
63                     +     ''
64                     +    '});'
65                     +'});'
66                     +'</script>'
67                     +'</head>'
68                     +'<body>'
69                     +'<h1>hello i am server b </h1>'
70                     +'<input id="getFo" type="button" value="获取个人粉丝"/>'
71                     +'</body>'
72                     +'</html>'; 73  res.write(html); 74  res.end(); 75     }else if(urlPath === '/followers.json'){ 76         res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'}); 77         var followers = { 78                                 "users" : [ 79                                     {"id" : "1","name" : "小王"}, 80                                     {"id" : "2","name" : "小李"} 81  ] 82  }; 83         var fjson = JSON.stringify(followers); 84  res.end(fjson); 85     }else{ 86         res.writeHead(404,{'Content-Type':'text/html;charset=utf-8'}); 87         res.end('page not found'); 88  } 89     
90 } 91     
92     
93 }).listen(9099); 94 console.log('Listening app B at 9099...');

 注意看48行和51行,48行定义了回调函数,51行经过<script>标签,请求不一样域的数据,其中传递参数callback=getFollowers

而后修改appA.js以下:

View Code
 1 var http = require('http'),  2      url = require('url'),  3      querystring = require('querystring');  4 
 5 http.createServer(function(req,res){  6     
 7     var path = url.parse(req.url).pathname;  8     var qs = querystring.parse(req.url.split('?')[1]),  9  json; 10     if(qs.callback){ 11         var followers = { 12                                 users : [{id:'1',name:encodeURIComponent('小王')}] 13  }; 14         var fjson = JSON.stringify(followers); 15  console.log(fjson); 16         json = qs.callback + "(" + fjson + ");"; 17         res.writeHead(200,{ 18                         'Content-Type':'application/json', 19                         'Content-Length' : json.length 20  }); 21  res.end(json); 22     
23  } 24     
25     if(path === '/index'){ 26         res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); 27         res.end('home'); 28     }else if(path === '/followers.json'){ 29         res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'}); 30         var followers = { 31                                 "users" : [ 32                                     {"id" : "1","name" : "小王"}, 33                                     {"id" : "2","name" : "小李"} 34  ] 35  }; 36         var fjson = JSON.stringify(followers); 37  res.end(fjson); 38     }else{ 39         res.writeHead(404,{'Content-Type':'text/html;charset=utf-8'}); 40         res.end('page not found'); 41  } 42     res.end('hello'); 43 }).listen(8088); 44 console.log('Listening app A at 8088...');

 第10行-23行,咱们处理了传递的参数,并将数据填充到函数参数,并发送到请求者那边。再次运行两个程序,刷新http://www.b.com:9099/index便直接获得a域下的数据了,彷佛成功了。可是,我不想立刻执行呀,我也要和前面同样,点击按钮再得到,怎么办?这个也简单。只须要当咱们点击的时候动态的引入<script>就能够了,修改click事件处理部分的代码:

1 $("#getFo").click(function(){ 2 $("<script><//script>").attr("src","http://www.a.com:8088/index?callback=getFollowers").appendTo("body"); 3 });

再次重启服务器,当点击按钮就能够获取数据了。接下来,咱们再看看jQuery又是如何处理JSONP的呢?

 

 

四 jQuery中处理JSONP

  要经过jQuery使用JSONP是很是方便的,只须要修改最开始的ajax部分代码以下:

1 $.ajax({ 2     url:"http://www.a.com:8088/index", 3     dataType:"jsonp", 4     jsonp:"callback", 5     type:"get", 6     success:function(json){ 7     alert(decodeURIComponent(json.users[0].name)); 8  } 9 });

其中,jsonp指明了querystring的key为callback,value若是不指定,jQuery会默认随机生成一个名称:

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

 

五 JSONP可能引发的安全性问题

  由JSONP可能引发的安全问题主要是可能会遭受CSRF/XSRF的攻击,而使得容易遭受该攻击的也偏偏是上文中一直提到的JSONP的特色--能够跨源访问资源。普通的CSRF/XSRF攻击,仅仅可能利用受攻击用户,骗取服务器的信任,这样就能够模拟受攻击者对服务器进行一些有危害的请求,例如修改受攻击者的我的信息。可是,因为浏览器同源策略的限制,在第三方“恶意网站”没法读取服务器返回的信息。也就是说,攻击者只能捣捣乱,可是他仍是获取不到受攻击者的敏感信息的(无XSS注入的前提下)。可是,若是服务器上某个请求使用了JSONP返回用户数据,可想而知,在第三方,或者任何方网站都能顺利的获取到。关于CSRF/XSRF攻击,就说到这里,具体实现方式就不展开了。

  除了CSRF/XSRF攻击外,另外使用JSONP的网站(相对于部署JSONP的服务器)也可能有安全性问题。 由于,经过上面的实验,咱们看到了,经过JSONP请求远程服务器后,返回的是一个在本网站当即执行的函数。至关于这个脚本直接被注入到当前页面了。若是远端网站中存在注入漏洞,那么后果可想而知了。为了防止这样的事情发生,可使用 JSON-P 严格安全子集使浏览器能够对 MIME 类别是“application/json-p”请求作强制处理。若是回应不能被解析为严格的 JSON-P,浏览器能够丢出一个错误或忽略整个回应。关于安全性的问题先说到这里,安全问题永远是一个矛与盾的问题,总之,在互联网上,没有绝对的安全。若是再展开下去又会引出一堆东西,因此今天就先不说了。至于如何防范JSONP容易受到的CSRF/XSRF攻击,笔者认为最简单有效的方法就是对于敏感信息不要使用JSONP,由于也没有实际遇到过,不知道什么更好的解决方案。今天就到这里了,但愿对你们有用~

相关文章
相关标签/搜索