坐峰怀雪灬

路漫漫其修远兮,吾将上下而求索。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

JSON和JSONP,浅析JSONP解决AJAX跨域问题

Posted on 2017-01-16 16:01  坐峰怀雪灬  阅读(423)  评论(0编辑  收藏  举报

说到AJAX就会不可避免的面临两个问题,第一个是AJAX以何种格式来交换数据?第二个是跨域的需求如何解决?这两个问题目前都有不同的解决方案,比如数据可以用自定义字符串或者用XML来描述,跨域可以通过服务器端代理来解决。

但到目前为止最被推崇或者说首选的方案还是用JSON来传数据,靠JSONP来跨域。而这就是本文将要讲述的内容。

JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种依靠开发人员的聪明才智创造出的一种非官方跨域数据交互协议。我们拿最近比较火的谍战片来打个比方,JSON是地下党们用来书写和交换情报的“暗号”,而JSONP则是把用暗号书写的情报传递给自己同志时使用的接头方式。看到没?一个是描述信息的格式,一个是信息传递双方约定的方法。

既然随便聊聊,那我们就不再采用教条的方式来讲述,而是把关注重心放在帮助开发人员理解是否应当选择使用以及如何使用上。

什么是JSON?

 前面简单说了一下,JSON是一种基于文本的数据交换方式,或者叫做数据描述格式,你是否该选用他首先肯定要关注它所拥有的优点。

JSON的优点:

1、基于纯文本,跨平台传递极其简单;

2、Javascript原生支持,后台语言几乎全部支持;

3、轻量级数据格式,占用字符数量极少,特别适合互联网传递;

4、可读性较强,虽然比不上XML那么一目了然,但在合理的依次缩进之后还是很容易识别的;

5、容易编写和解析,当然前提是你要知道数据结构;

JSON的缺点当然也有,但在作者看来实在是无关紧要的东西,所以不再单独说明。

JSON的格式或者叫规则:

JSON能够以非常简单的方式来描述数据结构,XML能做的它都能做,因此在跨平台方面两者完全不分伯仲。

1、JSON只有两种数据类型描述符,大括号{}和方括号[],其余英文冒号:是映射符,英文逗号,是分隔符,英文双引号""是定义符。

2、大括号{}用来描述一组“不同类型的无序键值对集合”(每个键值对可以理解为OOP的属性描述),方括号[]用来描述一组“相同类型的有序数据集合”(可对应OOP的数组)。

3、上述两种集合中若有多个子项,则通过英文逗号,进行分隔。

4、键值对以英文冒号:进行分隔,并且建议键名都加上英文双引号"",以便于不同语言的解析。

5、JSON内部常用数据类型无非就是字符串、数字、布尔、日期、null 这么几个,字符串必须用双引号引起来,其余的都不用,日期类型比较特殊,这里就不展开讲述了,只是建议如果客户端没有按日期排序功能需求的话,那么把日期时间直接作为字符串传递就好,可以省去很多麻烦。

JSON实例:

 1 // 描述一个人
 2 
 3 var person = {
 4     "Name": "Bob",
 5     "Age": 32,
 6     "Company": "IBM",
 7     "Engineer": true
 8 }
 9 
10 // 获取这个人的信息
11 
12 var personAge = person.Age;
13 
14 // 描述几个人
15 
16 var members = [
17     {
18         "Name": "Bob",
19         "Age": 32,
20         "Company": "IBM",
21         "Engineer": true
22     },
23     {
24         "Name": "John",
25         "Age": 20,
26         "Company": "Oracle",
27         "Engineer": false
28     },
29     {
30         "Name": "Henry",
31         "Age": 45,
32         "Company": "Microsoft",
33         "Engineer": false
34     }
35 ]
36 
37 // 读取其中John的公司名称
38 
39 var johnsCompany = members[1].Company;
40 
41 // 描述一次会议
42 
43 var conference = {
44     "Conference": "Future Marketing",
45     "Date": "2012-6-1",
46     "Address": "Beijing",
47     "Members": 
48     [
49         {
50             "Name": "Bob",
51             "Age": 32,
52             "Company": "IBM",
53             "Engineer": true
54         },
55         {
56             "Name": "John",
57             "Age": 20,
58             "Company": "Oracle",
59             "Engineer": false
60         },
61         {
62             "Name": "Henry",
63             "Age": 45,
64             "Company": "Microsoft",
65             "Engineer": false
66         }
67     ]
68 }
69 
70 // 读取参会者Henry是否工程师
71 
72 var henryIsAnEngineer = conference.Members[2].Engineer;

 关于JSON,就说这么多,更多细节请在开发过程中查阅资料深入学习。

 什么是JSONP?

 先说说JSONP是怎么产生的:

其实网上关于JSONP的讲解有很多,但却千篇一律,而且云里雾里,对于很多刚接触的人来讲理解起来有些困难,小可不才,试着用自己的方式来阐释一下这个问题,看看是否有帮助。

1、一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准;

2、不过我们又发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>);

3、于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

4、恰巧我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;

5、这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。

6、客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。

7、为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

如果对于callback参数如何使用还有些模糊的话,我们后面会有具体的实例来讲解。

JSONP的客户端具体实现:

 

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

 

到这里,应该明白了,JSON是一种轻量级的数据交换格式,像xml一样,是用来描述数据间的。JSONP是一种使用JSON数据的方式,返回的不是JSON对象,是包含JSON对象的javaScript脚本。

 

那JSONP是如何工作的呢,我们知道,由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源。若要跨域请求出于安全性考虑是不行的,但是我们发现,Web页面上调用js文件时则不受是否跨域的影响,而且拥有”src”这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>,这时候,聪明的程序猿就想到了变通的方法,如果要进行跨域请求, 通过使用html的script标记来进行跨域请求,并在响应中返回要执行的script代码,其中可以直接使用JSON传递 javascript对象。即在跨域的服务端生成JSON数据,然后包装成script脚本回传,着不就突破同源策略的限制,解决了跨域访问的问题了么。不管jQuery也好,extjs也罢,又或者是其他支持jsonp的框架,他们幕后所做的工作都是一样的,下面我来循序渐进的说明一下jsonp在客户端的实现:

Html前端:

 1  <script src="scripts/jquery-3.1.1.min.js"></script>
 2     <script>
 3         CallWebServiceByJsonp();
 4         function CallWebServiceByJsonp() {
 5             var strCparent = {
 6                 run: "UrlReferrer",
 7                 referrer:document.referrer
 8             }
 9             $.ajax({
10                 type: "Get",
11                 cache: false,
12                 async:false,
13                 url: "http://localhost:18065/WebService.asmx/Getinfo",
14                 data: JSON.stringify(strCparent),
15                 contentType: "application/json; charset=utf-8",
16                 dataType: "jsonp",
17                 jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
18                 jsonpCallback: "OnGetMemberSuccessByjsonp",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
19                 //success: function (data) {
20                 //    alert(JSON.stringify(data));
21                 //},
22                 //error: function (XMLHttpRequest, textStatus, errorThrown) {
23                 //    myAlert("请求数据异常,状态码:" + XMLHttpRequest.status);
24                 //}              
25             });
26         }
27         function OnGetMemberSuccessByjsonp(data) {
28             //处理data
29             alert(JSON.stringify(data));
30         }
31     </script>

 WebsService:

  1 using Newtonsoft.Json;
  2 using System;
  3 using System.Collections.Generic;
  4 using System.Linq;
  5 using System.Web;
  6 using System.Web.Script.Serialization;
  7 using System.Web.Script.Services;
  8 using System.Web.Services;
  9 
 10 namespace API
 11 {
 12     /// <summary>
 13     /// WebService 的摘要说明
 14     /// </summary>
 15     [WebService(Namespace = "http://tempuri.org/")]
 16     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
 17     [System.ComponentModel.ToolboxItem(false)]
 18     // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 
 19     // [System.Web.Script.Services.ScriptService]
 20     public class WebService : System.Web.Services.WebService
 21     {
 22 
 23         [WebMethod]
 24        [ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = true)]
 25         public void Getinfo()
 26         {
 27             HttpContext.Current.Response.ContentType = "application/json;charset=utf-8";
 28             string jsonCallBackFunName = HttpContext.Current.Request.Params["callback"].ToString();
 29             string strCparent = HttpUtility.UrlDecode(HttpContext.Current.Request.Params.ToString());
 30             //string jsonCallBackFunName1 = HttpContext.Current.Request.QueryString["callback"].Trim();
 31             //#region -把多个string拼接成json对象-
 32             //Dictionary<string, string> lt = new Dictionary<string, string>();
 33             //lt.Add("run", run);
 34             //lt.Add("referrer", referrer);
 35             //string strCparent = JsonConvert.SerializeObject(lt).ToString();
 36             //#endregion
 37             if (strCparent != "")
 38             {
 39                 if (strCparent.IndexOf("}") > 0)
 40                 {
 41                     strCparent = strCparent.Substring(strCparent.IndexOf("{"), strCparent.IndexOf("}") - strCparent.IndexOf("{") + 1);
 42                     //获取接口首字段Run值,识别命令执行方法
 43                     Run run = new Run();
 44                     JavaScriptSerializer serializer = new JavaScriptSerializer();
 45                     string x = HttpUtility.UrlDecode(strCparent);
 46                     run = serializer.Deserialize<Run>(HttpUtility.UrlDecode(strCparent));
 47 
 48                     //访问来源过滤
 49                     if (run.run == "UrlReferrer")
 50                     {
 51 
 52                         UrlReferrer tmp = new UrlReferrer();
 53 
 54                         tmp = serializer.Deserialize<UrlReferrer>(HttpUtility.UrlDecode(strCparent));
 55 
 56                         string referrer = tmp.referrer;
 57 
 58                         bool isfree = true;
 59                         string source = null;
 60                         string keyword = null;
 61 
 62                         if (referrer.Contains("baidu.com/baidu.php?"))
 63                         {
 64                             //百度广告
 65                             //wd=上海长城宽带
 66                             keyword = referrer.Substring(referrer.IndexOf("&wd=") + 4).Substring(0, referrer.Substring(referrer.IndexOf("&wd=") + 4).IndexOf("&"));
 67                             isfree = false;
 68                             source = "百度推广";
 69 
 70                         }
 71 
 72                         if (referrer.Contains("baidu.com/link?url"))
 73                         {
 74                             //百度自然流量,无法获取关键词
 75                             source = "百度自然";
 76                         }
 77 
 78                         if (referrer.Contains("so.com/link?url"))
 79                         {
 80                             //360广告
 81                             //q=长城宽带
 82 
 83                             keyword = (referrer.Substring(referrer.IndexOf("q=") + 2).IndexOf("&") >= 0) ? referrer.Substring(referrer.IndexOf("q=") + 2).Substring(0, referrer.Substring(referrer.IndexOf("q=") + 2).IndexOf("&")) : referrer.Substring(referrer.IndexOf("q=") + 2);
 84                             isfree = false;
 85                             source = "360推广";
 86                         }
 87 
 88                         if (referrer.Contains("so.com/link?url"))
 89                         {
 90                             //360自然流量
 91                             //q=长城宽带 
 92 
 93                             keyword = (referrer.Substring(referrer.IndexOf("q=") + 2).IndexOf("&") >= 0) ? referrer.Substring(referrer.IndexOf("q=") + 2).Substring(0, referrer.Substring(referrer.IndexOf("q=") + 2).IndexOf("&")) : referrer.Substring(referrer.IndexOf("q=") + 2);
 94                             source = "360自然";
 95 
 96                         }
 97 
 98                         if (referrer.Contains("sogou.com/link?url="))
 99                         {
100                             //搜狗自然流量
101                             //query=上海长城宽带
102                             keyword = (referrer.Substring(referrer.IndexOf("query=") + 6).IndexOf("&") >= 0) ? referrer.Substring(referrer.IndexOf("query=") + 6).Substring(0, referrer.Substring(referrer.IndexOf("query=") + 6).IndexOf("&")) : referrer.Substring(referrer.IndexOf("query=") + 6);
103                             source = "搜狗自然";
104                         }
105 
106                         UrlReferrer_Return urlreferrer_return = new UrlReferrer_Return { isfree = isfree, keyword = keyword, source = source };
107                         //JSON序列化
108                         string Data = serializer.Serialize(urlreferrer_return);
109 
110                         //下面代码必须
111                         HttpContext.Current.Response.Write(string.Format("{0}({1})", jsonCallBackFunName, Data));
112                         HttpContext.Current.Response.End();
113                     }
114                 }
115             }
116         }
117         [WebMethod]
118         public string HelloWorld()
119         {
120             return "Hello World";
121         }
122         public class Run
123         {
124             public string run { get; set; }
125 
126         }
127 
128         public class UrlReferrer
129         {
130             public string referrer { get; set; }
131         }
132 
133         public class UrlReferrer_Return
134         {
135             public bool isfree { get; set; }
136 
137             public string keyword { get; set; }
138 
139             public string source { get; set; }
140         }
141     }
142 }

Config加入如下配置:

1  <webServices>
2       <protocols>
3         <add name= "HttpPost"/>
4         <add name= "HttpGet"/>
5       </protocols>
6     </webServices>

 

如上所示,前端的CallWebServiceByJsonp方法采用jQuery的ajax方法调用后端的Web服务GetInfo方法,后台的GetInfo方法,使用前端的回调方法OnGetMemberSuccessByjsonp包装后台的业务操作的JSON对象,返回给前端一段javascript片段执行。巧妙的解决了跨域访问问题。

JSONP的缺点:

JSONP不提供错误处理。如果动态插入的代码正常运行,你可以得到返回,但是如果失败了,那么什么都不会发生。