一次關於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,由於也沒有實際遇到過,不知道什麼更好的解決方案。今天就到這裏了,但願對你們有用~

相關文章
相關標籤/搜索