大略的来说,出于安全方面的考虑,页面中的JavaScript无法访问其他做事器上的数据,即“同源策略”。而跨域便是通过某些手段来绕过同源策略限定,实现不同做事器之间通信的效果。
什么是JSONP?
JSON(JavaScript Object Notation) 是一种轻量级的数据交流格式,JSONP 是 JSON with padding(添补式 JSON 或参数式 JSON)的简写。JSONP(JSON with Padding)则是JSON 的一种“利用模式”,通过这种模式可以实现数据的跨域获取。JSONP 由两部分组成:回调函数和数据。回调函数是当相应到来时该当在页面中调用的函数。回调函数的名字一样平常是在要求中指定的。而数据便是传入回调函数中的 JSON 数据。
JSONP跨域的基本事理
在同源策略下,在某个做事器下的页面是无法获取到该做事器以外的数据的,但img、iframe、script等标签是个例外,这些标签可以通过src属性要求到其他做事器上的数据。利用script标签的开放策略,我们可以实现跨域要求数据,当然,也须要做事真个合营。当我们正常地要求一个JSON数据的时候,做事端返回的是一串JSON类型的数据,而我们利用JSONP模式来要求数据的时候,做事端返回的是一段可实行的JavaScript代码。
举个例子,如果须要从做事器(http://www.a.com/user?id=123)获取的数据如下:
{\"大众id\公众: 123, \公众name\"大众 : 张三, \公众age\"大众: 17}那么,利用JSONP办法要求(http://www.a.com/user?id=123?callback=foo)的数据将会是如下:
foo({\"大众id\公众: 123, \"大众name\公众 : 张三, \公众age\"大众: 17});当然,如果做事端考虑得更加充分,返回的数据可能如下:
try{foo({\"大众id\"大众: 123, \"大众name\"大众 : 张三, \"大众age\"大众: 17});}catch(e){}这时候我们只要定义一个foo()函数,并动态地创建一个script标签,使其的src属性为http://www.a.com/user?id=123?callback=foo:
便可以利用foo函数来调用返回的数据了。
JSONP跨域事理探秘
我们知道,利用 XMLHTTPRequest 工具发送HTTP要求时,会碰着 同源策略 问题,域不同要求会被浏览器拦截。
那么是否有方法能绕过 XMLHTTPRequest 工具进行HTTP跨域要求呢?
换句话说,不该用 XMLHTTPRequest 工具是否可以发送跨域HTTP要求呢?
细心的你可能会创造,像诸如:
<script type=\公众text/javascript\"大众 src=\"大众http://www.a.com/scripts/1.js\公众></script>
<img src=\"大众http://www.b.com/images/1.jpg\"大众 />
<link rel=\"大众stylesheet\公众 href=\"大众http://www.c.com/assets/css/1.css\"大众 />
这种标签是不会碰着\"大众跨域\"大众问题的,严格意义上讲,这不是跨域,跨域是指在脚本代码中向非同源域发送HTTP要求,这只是跨站资源要求。
那么,我们是否可以利用跨站资源要求这一办法来实现跨域HTTP要求呢?
以<script></script>标签为例进行探索,先看一段代码:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=\公众Content-Type\"大众 content=\公众text/html; charset=utf-8\"大众 />
<title>jsonp demo</title>
<!-- JavaScript片断1 -->
<script type=\"大众text/javascript\"大众>
function handler(data) {
alert(data);
// our code here...
}
</script>
<!-- JavaScript片断2 -->
<script type=\公众text/javascript\公众>
handler('success');
</script>
</head>
<body>
A JSONP demo.
</body>
</html>
这段代码中,有2个JavaScript片断,第1个片断中定义了一个处理函数handler(),这个处理函数比较大略,没有对数据做任何处理,只是把它alert出来;第2个片断调用了它,运行这个页面浏览器会弹出\"大众success\公众。
我们假设第2个JavaScript片断存储在别的地方,然后我们利用<script src=\"大众\公众 />的办法把它引入进来,像这样:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=\公众Content-Type\"大众 content=\"大众text/html; charset=utf-8\公众 />
<title>jsonp demo</title>
<!-- JavaScript片断1 -->
<script type=\公众text/javascript\公众>
function handler(data) {
alert(data);
// our code here...
}
</script>
<!-- JavaScript片断2 -->
<script type=\"大众text/javascript\公众 src=\"大众http://service.a.com/script/1.js\公众></script>
</head>
<body>
A JSONP demo.
</body>
</html>
service.a.com/script/1.js:
handler('success');
这种方法和把JavaScript代码直接写在页面是等效的,但是,我们由此可以遐想到什么?
我们是否可以事先在本页面定义处理程序,做事端返回JS脚本,脚本的内容便是对处理程序的回调,做事返回的数据通过参数的形式传回:
handler('做事返回的数据');
然后通过动态向当前页面head节点添加<script src=\"大众做事地址\公众></script>节点的办法来“假造”HTTP要求?
于是,可以编写这样一个大略的测试用例:
先写做事端,非常大略的一个做事,返回字符串\"大众Hello World\公众,一样平常处理程序Service.ashx:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace JSONPDemo.Service
{
/// <summary>
/// Service2 的择要解释
/// </summary>
public class Service2 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = \"大众text/plain\"大众;
context.Response.Write(\"大众handler('Hello World');\公众);
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
再写客户端,一个大略的静态Web页,index.html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=\公众Content-Type\公众 content=\"大众text/html; charset=utf-8\"大众/>
<title></title>
<script type=\"大众text/javascript\"大众>
// 跨域发送HTTP要求,从做事端获取字符串\"大众Hello World\公众
function getHello() {
var script = document.createElement('script');
script.setAttribute('src', 'http://localhost:8546/Service.ashx');
document.querySelector(\"大众head\公众).appendChild(script);
}
// 处理函数
function handler(data) {
alert(data);
// our code here...
}
</script>
</head>
<body>
<input type=\"大众button\"大众 value=\公众发送跨域HTTP要求,获取Hello World\公众 onclick=\"大众getHello()\公众 />
</body>
</html>
测试成功!
在这个测试例子中,我们利用一样平常处理程序编写了一个大略的返回Hello World的做事,然后利用动态创建<script></script>节点的办法实现了跨域HTTP要求。
由于<script>、<img>标签资源要求是异步的,以是我们就实现了一个跨域的异步HTTP要求。
但是这么做是不足的,一个页面可能会有多个HTTP要求,而上面这个示例的处理程序只有一个——handler。
不同的要求该当由不同的处理程序来处理,对上面的代码稍做修正,只须要给<script>标签的src属性中的URL添加一个参数来指定回调函数的名称就可以了:
做事端:
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = \"大众text/plain\公众;
// 前端指定的回调函数名称
var callbackFuncName = context.Request.QueryString[\"大众callback\"大众];
var responseData = \公众Hello World\公众;
// 回调脚本,形如:handler('responseData');
var scriptContent = string.Format(\"大众{0}('{1}');\"大众, callbackFuncName, responseData);
context.Response.Write(scriptContent);
}
Web客户端:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=\公众Content-Type\"大众 content=\"大众text/html; charset=utf-8\"大众/>
<title>jsonp demo</title>
<script type=\公众text/javascript\"大众>
// 跨域发送HTTP要求,从做事端获取字符串\"大众Hello World\"大众
function getHello() {
var script = document.createElement('script');
script.setAttribute('src', 'http://localhost:8546/Service.ashx?callback=handler');//callback指定回调函数名称
document.querySelector(\"大众head\"大众).appendChild(script);
}
// 处理函数
function handler(data) {
alert(data);
// our code here...
}
</script>
</head>
<body>
<input type=\"大众button\公众 value=\"大众发送跨域HTTP要求,获取Hello World\公众 onclick=\公众getHello()\"大众 />
</body>
</html>
利用jQuery的JSONP跨域
jQuery的ajax方法对JSONP式跨域进行了封装,如果利用jQuery进行JSONP事理式的跨域HTTP要求,将会变得非常大略:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=\"大众Content-Type\"大众 content=\"大众text/html; charset=utf-8\"大众/>
<title>jQuery jsonp demo</title>
<script src=\公众jquery-1.11.0.min.js\公众></script>
<script type=\"大众text/javascript\公众>
$.ajax({
type: \"大众get\公众,
async: false,
url: \公众http://localhost:8546/Service.ashx\"大众,
dataType: \"大众jsonp\公众,
success: function (data) {
alert(data);
},
error: function () {
alert('fail');
}
});
</script>
</head>
<body>
利用jQuery统统将会变得非常大略。
</body>
</html>
只须要将dataType设置为\"大众jsonp\公众就可以进行跨域要求了,统统就像发送非跨域要求那样大略。
jQuery为我们封装好了回调函数,一样平常情形下不须要我们单独去写,如果你不想在success中处理,想单独写处理函数,那么可以通过设置这2个参数来实现:
jsonp: \"大众callback\公众,//通报给做事真个回调函数名称参数,如果不设置此项,则默认是\"大众callback\"大众jsonpCallback: \公众handler\公众,//通报给做事真个回调函数名称,如果不设置此项,则默认是形如\"大众jQuery111007837897759742043_1460657212499\"大众的由jQuery自动天生的函数名称必须要强调的是:
1.JSONP虽然看起来很像一样平常的ajax要求,但其事理不同,JSONP是对文章第一小节事理的封装,是通过<script>标签的动态加载来实现的跨域要求,而一样平常的ajax要求是通过XMLHttpRequest工具进行;
2.JSONP不是一种标准协议,其安全性和稳定性都不如 W3C 推举的 CORS;
3.JSONP不支持POST要求,纵然把要求类型设置为post,实在质上仍旧是一个get要求。