在进行AJAX的时候会常常产生这样一个报错:javascript
看红字,这是浏览器的同源策略,使跨域进行的AJAX无效。注意,不是不发送AJAX请求(其实就是HTTP请求),而是请求了,也返回了,但浏览器‘咔擦’一声,下面没有了。对比下fiddler和浏览器抓的包的异同:
fiddler:html
chrome:java
简而言之,浏览器这边就是头(response header)给看,身体(response body)不给看。ajax
什么是同源策略?为何会有同源策略?这一点在吴翰清老师著的《白帽子讲Web安全》一书中由阐述,这里就不赘述了。下面要作的,就是使用JSONP让上面的报错消失,按正确的流程进行下去。chrome
首先介绍下我这里的环境,两个Web服务器,Tomcat监听8081,Node监听3000,Tomcat这边实现一个处理AJAX的JSP文件,很简单,返回一个JSON
json
<%@ page contentType="application/json; charset=utf-8" %> {"status": true}
Tomcat的页面对这个URL发出AJAX请求,并打印出了返回值跨域
但Node的页面发出AJAX请求,则像上面那样报错了,由于AJAX有同源策略保护。怎么绕过这个保护呢?平时咱们页面引入的CSS、JS多是从其余的服务器好比静态服务器、CDN获取内容,都在不一样的域,可知页面内的标签引入JS是没有同源策略一说的,并且也是进行request和处理response,因而咱们把这个AJAX请求改成以下代码:浏览器
var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp'; document.body.insertBefore(script, document.body.lastChild);
但仍是残忍的报错了安全
由于返回的JSON({"status": true})成为了一个独立的js片断,而这个片断明显是不符合语法的,若是返回的是符合语法规范的处理JSON的js片断而不单单是JSON就行了。好比咱们将服务器端的代码改为这样:服务器
<%@ page contentType="application/javascript; charset=utf-8" %> console.log({"status": true});
再在Node的页面进行AJAX
目的是达到了,但问题是,这个AJAX的servlet不只返回了数据,还返回了行为,难道我要把处理DOM的js写在这里面吗?页面重构了又跑到这里来修改?问题太美不敢想,因此请求成功的方法必须写在页面的js里面,好比这样
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp'; document.body.insertBefore(script, document.body.lastChild);
而服务器返回的js片断直接调用这个function就好了,这个就叫回调函数了
<%@ page contentType="application/javascript; charset=utf-8" %> callback({"status": true});
能够看到,这个方案比以前好多了,servlet和请求页面的耦合度低了不少,但没彻底解决,好比callback这个回调函数的名字,若是把这个名字放在请求的parameter中,好比这样
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp?cb=callback'; document.body.insertBefore(script, document.body.lastChild);
服务器对这个parameter进行处理
<%@ page contentType="application/javascript; charset=utf-8" %>
<%= request.getParameter("cb") %>({"status": true});
优化一下,对没有cb参数的请求仅返回JSON
<% String callback = request.getParameter("cb"); if(null == callback) { response.setContentType("application/json; charset=utf-8"); %> {"status": true} <% }else { response.setContentType("application/javascript; charset=utf-8"); %> <%= callback %>({"status": true}) <% } %>
那么整个JSONP的功能就实现了。但还有一点瑕疵,代码执行完html中留下了一个script标签,强迫症能忍?处女座能忍?
解决方法:可使用jQuery的方法,jQuery会清除掉留下的script标签。
$.ajax({ url: 'http://localhost:8081/test/true.jsp', dataType: "jsonp", jsonp: "cb", success: function (data) { console.log(data) } });
也能够本身实现一个,我抛个砖,在js加载完成后删除节点。
function callback(data) { console.log(data); } var script = document.createElement('script'); script.src = 'http://localhost:8081/test/true.jsp?cb=callback'; document.body.insertBefore(script, document.body.lastChild); script.onload = function(){ this.parentNode.removeChild(this); };
至此,不知道有人发现没有,JSONP这种方式有一个致命的缺陷:就是因为它是经过引入script节点实现的,因此只支持GET方法。若是你任性,你无理取闹,你必定要用post跨域,那么只能考虑使用CORS了。
JSONP的东西就到此结束了,其实作完才发现,实际上这是个很简单的概念,取了个比较唬人的名字而已。