跨域(一):JSONP 解决跨域问题

跨域

协议、域名、端口都相同才算同域,否则就是跨域。

浏览器为了安全考虑(同源策略),不允许 axaj 跨域获取数据。浏览器会报错 xxx has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

但是可以跨域获取文件内容,例如 script 标签、 img 标签可以跨域使用,利用这一点,就可以用 JSONP 进行跨域请求,解决跨域问题。

JSONP

JSONP ( JSON with Padding ) 是 json 的一种”使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

JSONP 由两部分组成:回调函数+数据 。

JSONP 的原理就是动态添加一个 script 标签,而 script 标签的 src 属性是没有跨域的限制的。

后台代码

后台需要用回调函数名称包裹返回数据,这样,返回数据就作为回调函数的参数传回去了。

传统方法:

1
2
3
4
5
6
7
@RequestMapping("/test")
@ResponseBody
public String test(String callback) {
Map<String, String> map = new HashMap<String, String>();
map.put("msg", "hello world!");
return callback + "(" + JSON.toJSON(map) + ")";
}

spring 4.1 后新特性 :

1
2
3
4
5
6
7
8
9
@RequestMapping("/test")
@ResponseBody
public Object test(String callback) {
Map<String, String> map = new HashMap<String, String>();
map.put("msg", "hello world!");
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(JSON.toJSON(map));
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}

前端调用

1.利用 script 标签调用

1
2
3
4
5
6
<script type="text/javascript">
function jsonpCallback(result) {
console.log(result);
}
</script>
<script type="text/javascript" src="http://192.168.10.147:8080/zyjyly-1.0/dataApplication/test.do?callback=jsonpCallback"></script>

客户端注册一个 callback ,这里是 jsonpCallback ,然后把 callback 的名字传给服务器,服务端得到这个函数名之后,要用这样的方法

``` 包裹要输出的 json 内容。 此时服务器生成的json数据才能被客户端正确接收。
1
2
3
4
5

运行结果:

``` json
{msg: "hello world!"}

2.jQuery 的实现(普通 ajax 方式)

原理是一样的,只不过我们不需要手动的插入 script 标签以及定义回调函数。
jquery 会自动生成一个全局函数来替换 callback=? 中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。

1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
$(function() {
$.ajax({
type:'get',
dataType: 'jsonp',
url: 'http://192.168.10.147:8080/zyjyly-1.0/dataApplication/test.do',
success: function (result) {
console.log(result);
}
});
});
</script>

需要注意下,jsonp 只能用 get 方式去提交。

运行结果:

1
{msg: "hello world!"}

我们调试下后台代码,可以看到,jquery 会自动生成一个函数,去替换 callback=? 中的问号。

当然,我们也可以自定义 callback 的参数名和回调函数的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
$(function() {
$.ajax({
type:'get',
dataType: 'jsonp',
jsonp: "myCallBack", // 指定 callback 的参数名
jsonpCallback: "showData", // 指定回调函数的名称
url: 'http://192.168.10.147:8080/zyjyly-1.0/dataApplication/test.do',
success: function (result) {
console.log("success");
}
});
});
function showData(result){
console.log(result);
}
</script>

这时候,后台方法的参数就要稍微修改下:

1
2
3
4
5
6
7
8
9
@RequestMapping("/test")
@ResponseBody
public Object test(String myCallBack) {
Map<String, String> map = new HashMap<String, String>();
map.put("msg", "hello world!");
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(JSON.toJSON(map));
mappingJacksonValue.setJsonpFunction(myCallBack);
return mappingJacksonValue;
}

运行结果如下:

1
2
{msg: "hello world!"}
success

3.jQuery 的实现( $.getJSON 方式)

$.getJSON 方法会自动判断是否跨域,不跨域的话,就调用普通的 ajax 方法;跨域的话,则会以异步加载 js 文件的形式来调用 jsonp 的回调函数。

1
2
3
4
5
6
7
<script type="text/javascript">
$(function () {
$.getJSON("http://192.168.10.147:8080/zyjyly-1.0/dataApplication/test.do?callback=?", function (result) {
console.log(result);
});
});
</script>

需要注意下,参数后面必须带上 callback=? 。

JSONP 的弊端:

  • 要对服务器的代码进行改动
  • 只支持 GET 方法(原理是动态创建 script 来进行请求的)
  • 发送的不是 XMLHttpRequest 请求( XMLHttpRequest 请求有很多好用的特性)

总结

一句话就是利用 script 标签绕过同源策略,获得一个类似这样的数据,callback 是页面存在的回调方法,参数就是想得到的 json。

1
callback({"msg": "hello world!"})