工作中我们经常会遇到ajax跨域的问题,今天我们了解一下跨域的原因和各种解决方案
跨域原因
所谓跨域就是:在a.com域下,通过ajax请求访问b.com域下的资源,出于安全的考虑,浏览器同源策略允许跨域写,而不允许跨域读,写就是上行,发送请求,send request,读就是下行,接受响应,receive response;
通俗点就是:a域向b域发送ajax请求,可以请求成功,但在请求的结果返回时,浏览器会先去判断a域与b域是否是同一个域下,“是”则返回数据,“否”则拦截数据并抛出如下异常信息
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
解决方案
主流解决方案有
jsonp
后台代理
cors
其他解决方案有
tomcat设置过滤器(基于cors)
nginx设置(基于cors)
chrome通过命令屏蔽跨域安全检查
chorme通过插件实现跨域(基于cors)
JSONP
原理:
浏览器的同源策略中虽然限制了不同域之间无法通过ajax请求得到响应结果,但有些html标签可以突破该限制,如img,srcipt,iframe标签中的src属性,可以引用不属于本域的资源且执行成功,jsonp的原理就是利用src属性的特性,动态的创建这些标签发送请求,接收响应后执行回调
案例:在a域中向b域发送ajax请求
a域:前端js
//定义回调函数 function myCallBack(data){ alert("回调的数据为:"+data); } //按钮点击事件 function btnClick(){ //创建script标签 var script = document.createElement('script'); //b域的资源路径 script.src = 'http://www.b.com/getData?callback=myCallBack'; // 在文档中追加script标签,开始发送请求到后台,响应结果后会自动调用myCallBack document.body.appendChild(script); }
a域:html
<input type="button" onclick="btnClick" value="请求资源"/>
b域:java后端代码
//映射请求路径 @RequestMapping("/getData") @ResponseBody public String getData(String callback){ //获取回调函数名后,拼接函数加数据返回 return callback+"('访问成功')"; }
流程:
1.a域前端定义回调函数和点击事件
2.当a域用户点击按钮后,触发点击事件,创建script对象且向b域后端发送请求
3.当b域后端接收回调函数名后,拼接结果返回
4.执行a域前端中的回调函数
优点:
1. 环境依赖少,只需要浏览器兼容js就可以执行
2.兼容性强,基本所有的浏览器都可以采用该方案(包括IE6,7等老式浏览器)
缺点:
1.只能执行get类型http请求,非get请求无法采用该方案
2.需要b域后端代码配合,侵入性强,在b域后端代码不可控的情况下无法采用
其他:
jquery中的jsonp原理与上面案例类似,只是在发送请求的过程中会帮我们动态创建回调函数,在调用完成后删除回调函数减少内存消耗,当然也可以通过设置指定回调函数名称传给后台
后台代理
原理:http请求可以正常访问跨域的资源,如果a域想访问b域的资源,可以在a域中的前端发送ajax请求到a域的后端,在a域的后端中发送http请求到b域获取资源,然后将资源返回给a域前端
案例:
a域:html
<
input
type
=
"button"
onclick
=
"btnClick"
value
=
"请求资源"
/>
a域:前端js
<script src="/javascripts/jquery.js" type="text/javascript"></script> <script type="text/javascript"> //按钮点击事件 function btnClick(){ //发送ajax请求到a域自己的后端 $.post("http://www.a.com/getData",function(data){ alert(data); }); } </script>
a域:后端代码(java)
//映射请求路径 @RequestMapping("/getData") @ResponseBody public String getData(){ CloseableHttpClient httpClient= null; String result=null; try { httpClient= HttpClients.createDefault(); HttpGet getMethod=new HttpGet(new URI("http://www.b.com/getData")); //请求b域资源 得到响应对象 HttpResponse response= httpClient.execute(getMethod); result= EntityUtils.toString(response.getEntity(), "utf-8"); } catch (IOException e) { e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); }finally { if(httpClient!=null){ try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } //将b域资源返回给a域 return result; } }
b域:后端代码(java)
//映射请求路径 @RequestMapping("/getData") @ResponseBody public String getData(){ /** * 省略业务逻辑代码 */ return "success"; }
流程
1.用户在a域点击按钮,触发点击事件,向a域后端发送ajax请求
2.a域后端发送http请求到b域后端请求资源
3.请求b域资源成功后将结果返回a域前端,a域前端输出资源
优点:
1.可以使用get,post等多种请求方式
2.b域后端不用配合a域做返回结果的修改
缺点:
1.性能较低,a域每一次请求b域资源,本质会发送2次请求,1次是发送ajax请求到a域自己后端,1次是a域后端发送请求到b域
2.代码复杂,不利于维护。当a域向b域请求资源比较多的情况下,每一个请求都要写一个后端代理,在代码量和维护上都是一个考验,该方案只适合跨域请求资源少,且b域不可控(即无法控制b域后端拼接结果返回)的情况下使用