Ajax的跨域请求数据的问题,一直是前端开发者经常讨论的话题。翻看了很多博客文章,发现很多人认为ajax跨域问题是Ajax本身的一些缺陷导致的,还有人认为这是服务器对Ajax请求的拦截,不过这些认识都是不全面的。其实禁止跨域请求是浏览器本身的一种安全策略——换句话说,其实禁止跨域不是什么ajax缺陷,是浏览器会对JavaScript的跨域请求有一些限制。
一、一些和跨域有关的概念
1.同源策略(same-origin policy)
说到同源策略,不得不提到另外一个概念——源(Origin)。那什么是源的呢?源其实是是个域名(domain),一般请求网页的那个url的域名就会被制定为源——例如这篇博客页面的的源就是“http://blog.csdn.net”。如此看来同源策略就能很容易理解了,就是限制Javascript的Ajax请求与源不相同的url。
2.跨域资源共享(Cross-Origin-Resource-Sharing)
跨域资源共享(CORS)机制,是为了浏览器能更为安全的处理跨域请求,使其不受同源策略的限制。简单来说就是把需要允许跨域的源写入response头里的Access-Control-Allow-Origin。对应的源就可以实现跨域资源共享了。
二、允许跨域和禁止跨域的具体机制
我们可以写一个简单的例子来说明这个问题,用servlet写一个简单的接口:
-
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
InputStream ipt = request.getInputStream();
-
try (InputStreamReader isr = new InputStreamReader(ipt); BufferedReader br = new BufferedReader(isr)) {
-
String line = null;
-
StringBuilder sb = new StringBuilder();
-
while ((line = br.readLine()) != null) {
-
sb.append(line);
-
}
-
System.out.println(sb.toString());
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
response.getWriter().write("{\"response\": \"response you\"}");
-
}
-
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
doGet(request, response);
-
}
之后把服务跑在127.0.0.1:8080下,之后用浏览器打开网上任意一个网页在控制台里发起对本地服务的ajax请求。很明显这是一个跨域的请求:
-
var xhr = new XMLHttpRequest();
-
xhr.open('POST', 'http://127.0.0.1:8080/cors-demo/Cors');
-
xhr.send('{"request": "hi"}');
-
xhr.onload = function(e) {
-
var xhr = e.target;
-
console.log(xhr.responseText);
-
}
浏览器控制台很快的返回了这个跨域常见的报错:
XMLHttpRequest cannot load http://127.0.0.1:8080/cors-demo/Cors.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://write.blog.csdn.net' is therefore not allowed access
其实,这个ajax请求是被服务端拿到了的,你是可以在后台的拿到前台send方法里面传入的{"request": "hi"}的,但是在Javascript里面却拿不到服务端的response。这应该怎么办呢?我们可以在服务端加上这句话:
response.addHeader("Access-Control-Allow-Origin", "http://write.blog.csdn.net");
这句话的意思是说,在response头里写入上述信息,允许源为“http://write.blog.csdn.net”的网页发起Ajax请求。之后便可以的发起跨域的ajax请求并正常的接受数据了。
综合前后端代码来看我们可以发现,其实这个request是发到服务端了,response也返回了客户端,但是浏览器会把当前网页的源与Access-Control-Allow-Origin里的url进行比较,如果发现有一样的url,就判定允许跨域访问,否则就无法在Javascript中拿到response信息,就会造成所谓的“禁止跨域访问”的现象。如果你想让一个web接口允许任何源的跨域访问,你就可以把头信息写成:Access-Control-Allow-Origin: * —— “*”表示任意的源。其实浏览在禁止跨域的访问里报的异常也能很好的帮助我们理解这个问题,上文的报错信息的大概意思就是:没有在response的头信息里找到Access-Control-Allow-Origin这个属性,所以源 “****” 没有权限访问 ”*****“ url。
三、为什么jsonp能解决跨域问题
不过大部分前端开发人员一遇到跨域问题第一反应想到的解决方案还是jsonp。jsonp是个非正式的传输协议,jsonp之所以能够绕过浏览器的同源策略是因为除了JavaScript里的Ajax和Fetch请求,其他的请求都不受同源策略的限制——换句话说除了ajax请求,网页中其他的如css,js脚本等资源文件的请求,不会因为跨域而被限制访问。jsonp正是利用了这一点,完成了跨域数据请求。
那如何制造一个jsonp的请求呢?下面是例子:
前端代码:
-
var callback = 'callBkFunc';
-
this[callback] = function(result) {
-
console.log(result);
-
}
-
var JSONP = document.createElement('script');
-
JSONP.type = 'text/javascript';
-
JSONP.src = "http://127.0.0.1:8080/cors-demo/Cors?callback=" + callback;
-
document.getElementsByTagName("head")[0].appendChild(JSONP);
大致流程就是声明一个script标签,并把资源路径设为我们想跨域访问的url。在声明一个方法名为“callBkFunc”的方法名,并把其作为一个参数传给后台。
服务端代码:
-
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
System.out.println(request.getParameter("callback"));
-
String callbackName = request.getParameter("callback");
-
response.getWriter().write(callbackName + "({\"response\": \"response you\"});");
-
}
在前端通过script标签向后台传了方法名之后,服务端代码用得到的这个方法名构造了一个动态的js脚本——也就是“callBkFunc(args)”。在args写的就是我们想传给前端的数据,之后前端就可以在callBkFunc定义的形参result里拿到后台传输给前端的数据了。
转自:https://blog.csdn.net/u011037503/article/details/78025072