ajax跨域访问方案分为jsonp和cors,本文将会对两种方案进行介绍。
jsonp
jsonp的原理是利用html文档script标签可以跨域访问的特点,通过在HTML文档中动态添加script标签,从其它域获取数据并在原HTML文档中回调处理数据。下面利用jquery封装好的jsonp,跨域获取数据。
清单:www.a.com的html文档
<!DOCTYPE html> <html> <head> <Meta charset="UTF-8"> <title>AJAX Tester</title> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript"> $(function () { // jsonp形式的请求 $.ajax({ url: "http://www.b.com/user_info?callback=?",data: {id: 1},dataType: "jsonp" }).done(showResult); }); function showResult(ret) { console.log(ret); $("#container").html(ret.name); } </script> </head> <body> My name is: <span style="font-weight:bold;" id="container"></span> <script type="text/javascript"> </script> </body> </html>
代码中,采用ajax jsonp的方式请求www.b.com域名获取数据,并显示在页面中。 JSONP默认采取get方式请求数据。
清单:www.b.com的API
@WebServlet("/user_info") public class GetUserInfoServlet extends HttpServlet { protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { // 要正确设置响应类型,避免IE出现下载 response.setContentType("application/javascript"); String userInfo = "{\"id\":1,\"name\":\"zhangsan\"}"; String ret; // 通过参数传递回调函数名,一定程度降低了前后端代码的耦合度 String callback = request.getParameter("callback"); if (callback != null && !callback.isEmpty()) { ret = callback + "(" + userInfo + ")"; //拼接jsonp字符串 } else { ret = userInfo; //正常返回json数据 } response.getWriter().write(ret); } @Override protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { doGet(request,response); } }
JSONP的返回数据的格式是 callback_method(json_data) ,这里的callback_method对应于原html文档中存在的一个函数的函数名。
缺点: jsonp采用的是get方法请求数据,当数据量很大的时候,不能解决跨域请求数据的问题。
CORS
CORS,全称跨域资源共享(Cross-Origin Resource Sharing)。CORS 是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同的域访问其资源。
既然CORS是一种规范,那么必须需要浏览器实现这种规范,这就不可避免出现浏览器兼容的问题。我们看一下各浏览器对CORS的支持
CORS请求又分为简单请求和非简单请求。简单请求是指通过GET方式获取资源;非简单请求是指 如POST、PUT、DELETE类型请求,或者在请求中有其它自定义的HTTP Header。下面详细介绍一下对于不同的请求的实现方法。
简单请求
response中需要添加Access-Control-Allow-Origin头,告诉浏览器哪些域名的网站可以获取返回的数据
response.addHeader("Access-Control-Allow-Origin","*"); //可以跨域访问的域名
清单:www.a.com的html文档获取www.b.com的数据
<!DOCTYPE html> <html> <head> <Meta charset="UTF-8"> <title>AJAX Tester</title> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript"> $(function () { $.ajax({ url: "http://www.b.com/user_info_2",method: "GET" }).done(showResult); }); function showResult(ret) { $("#container").html(ret.name); } </script> </head> <body> My name is: <span style="font-weight:bold;" id="container"></span> </body> </html>
清单:www.b.com通过cors方式返回数据给www.a.com
@WebServlet("/user_info_2") public class GetUserInfoServlet2 extends HttpServlet { protected void doGet(HttpServletRequest request,IOException { // 简单请求,直接设置Access-Control-Allow-Origin就可以了 response.addHeader("Access-Control-Allow-Origin","*"); //可以跨域访问的域名 // 要正确设置响应类型,避免IE出现下载 response.setContentType("application/json"); response.getWriter().write("{\"id\":1,\"name\":\"zhangsan\"}"); } @Override protected void doPost(HttpServletRequest request,response); } }
非简单请求
对于非简单请求,会执行一次preflight操作,浏览器会发起一次OPTIONS类型请求,要对OPTIONS请求给予正确应答,应答内容是允许谁访问,允许什么方法,允许什么自定义头的request。下面看一下一次request请求的CORS流程。
打开调试模式后,可以看到一次request请求被分成了两次:
第一个请求是用OPTIONS方法。 //这里所做是的是预检查,从服务器确认是否可以继续
第二个请求才是你发起的请求方法。
清单:www.a.com的html文档,对www.b.com发起ajax请求数据,并携带了自定义 HEADER和采用POST方法
<!DOCTYPE html> <html> <head> <Meta charset="UTF-8"> <title>AJAX Tester</title> <script type="text/javascript" src="js/jquery.js"></script> <script type="text/javascript"> $.ajax({ url: "http://www.b.com/user_info_2",method: "POST",beforeSend: function (xhr) { //添加自定义头 console.log("before send"); xhr.setRequestHeader("myheader","abc") } }).done(showResult); }); function showResult(ret) { $("#container").html(ret.name); } </script> </head> <body> My name is: <span style="font-weight:bold;" id="container"></span> </body> </html>
清单:www.b.com的API在option的处理中,对response添加了一些头。
@WebServlet("/user_info_2") public class GetUserInfoServlet2 extends HttpServlet { @Override protected void doOptions(HttpServletRequest request,IOException { System.out.println("OPTIONS"); response.addHeader("Access-Control-Allow-Origin","*"); response.addHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,DELETE"); response.addHeader("Access-Control-Allow-Headers","myheader,myheader2"); } protected void doGet(HttpServletRequest request,IOException { // 要正确设置响应类型,避免IE出现下载 response.setContentType("application/json"); response.getWriter().write("{\"id\":1,response); } }
大家可以看到利用CORS方案处理ajax跨域请求的问题,但是比较麻烦的是需要修改java代码,侵入性很强。为了解决这个问题,我们可以在Nginx中配置,让response返回的时候自动添加返回头。
niginx的配置如下:
if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,Content-Type'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET,Content-Type'; }