jsonp跨域请求详解——从繁至简

时间:2022-11-21 21:53:25

jsonp跨域请求详解——从繁至简

原文链接:https://zhuanlan.zhihu.com/p/24390509
详细讲述跨域请求的由来与演变——由hinesboy分享

     

什么是jsonp?为什么要用jsonp?

JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的

$.get("http://169.254.200.238:8080/jsonp.do", function (data) {
console.log(data);
});

此时浏览器抛出异常
jsonp跨域请求详解——从繁至简
因为两者的端口号分别为8080、8020 并不同源,从报错中也可以看出。

但是,我们换一种方式请求:

<script type="text/javascript" src="http://169.254.200.238:8080/jsonp.do">
</script>

jsonp跨域请求详解——从繁至简
可以看到,此时同样的请求确成功了!由此,我们可以得出可以进行跨域请求,这是jsonp的基础,但是浏览器同样抛出了语句不合法的异常,
jsonp跨域请求详解——从繁至简
那是因为我们请求的数据会立马被浏览器当作javascript语句去执行(谁让我们用

 callback( {"result":"success"} )

其中{“result”:”success”} 是我们想要获取的数据,浏览器会立即执行callback这个函数,此时,我们已经定义好了函数名为callback这个函数:

function callback(data){
// data为返回数据
// TODO 解析数据
}

这样是不是一切都说的通了!
所以jsonp跨域请求的关键就在于:

服务端要在返回的数据外层包裹一个客户端已经定义好的函数

ajax跨域请求实例

理解了上述内容,你就已经掌握了跨域请求的原理,那么下面我们将利用ajax发起跨域请求,服务端通过spring MVC处理jsonp请求。

//通过JQuery Ajax 发起jsonp请求
(注:不是必须通过jq发起请求 ,
例如:Vue Resource中提供 $.http.jsonp(url, [options]))
$.ajax({
// 请求方式
type: "get",
// 请求地址
url: "http://169.254.200.238:8080/jsonp.do",
// 标志跨域请求
dataType: "jsonp",
// 跨域函数名的键值,即服务端提取函数名的钥匙(默认为callback)
jsonp: "callbackparam",
// 客户端与服务端约定的函数名称
jsonpCallback: "jsonpCallback",
// 请求成功的回调函数,json既为我们想要获得的数据
success: function(json) {
console.log(json);
},
// 请求失败的回调函数
error: function(e) {
alert("error");
}
});
@RequestMapping({"/jsonp.do"})
public String jsonp(@RequestParam("callbackparam") String callback){
// callback === "jsonpCallback"
return callback + "({\"result\":\"success\"})";
}

此时,客户端接收到的返回值为:
jsonp跨域请求详解——从繁至简
这就是一个完整的跨域请求,但是这样就结束了吗?

细心的同学可能会发觉,现在如果不通过跨域去请求jsonp.do这个接口,不是就报错了吗?

是的,现在这个接口仅仅只能被携带callbackparam这个参数的请求。

为了解决这个问题,我们要判断请求的来源,

return  (request.from === jsonp) ? callback(data) : data ;

这才是我们想要的结果,解决这个问题需要我们对每个接口都做判断,或者通过AOP等等方式实现统一处理,这样做好像并不优雅。
在spring4.2以上的版本,支持了CORS(跨域资源共享)

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
为什么说它优雅呢?

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。 只要服务器实现了CORS接口 ,就可以跨源通信。

所以我们客户端可以像什么都没发生一样,依旧漠不关心的发送我们的请求:

// 不用担心跨域问题
$.ajax({
// 请求方式
type: "get",
// 请求地址
url: "http://169.254.200.238:8080/jsonp.do",
// 此时依然请求json格式数据 而非jsonp
dataType: "json",
// 请求成功的回调函数
success: function(json) {
console.log(json);
},
// 请求失败的回调函数
error: function(e) {
alert("error");
}
});

而我们用spring MVC实现的服务端也出乎意料的简洁(基于xml):

// spring配置文件 spring必须为4.2以上版本
<mvc:cors>
<mvc:mapping path="/**" />
</mvc:cors>

注意:通过以上两步我们已经完成了跨域请求操作。

这里,我对整个项目添加了跨域支持 , path 为支持跨域的路径 , 同样你可以在这里做详细配置:

<mvc:cors>

<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="false"
max-age="123" />

<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />

</mvc:cors>

当然,spring也支持通过java的方式进行配置:

// 此种方式等同于xml全局配置
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}

// 此种方式为详细配置
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}

pring 也允许针对某个controller类 或者 方法进行 跨域 ,通过@Configuration注解完成。

这里就不做详细解释,详情可以参考
SpringMvc解决跨域问题 - 王念博客 - 开源中国社区