摘要: 没有多么高深,就是沉淀些东西– –2016/08/某天自oschina blog
蚂蚁金服的实习即将结束,将知识总结一下。
我们这个项目前端使用antD,antD是采用React封装的一套组件库,目前开源http://ant.design/,所有组件都是拿来即用,大大缩短了开发周期,强烈推荐。React是单页面应用,通过ajax与后台通信,而antD调试部署在8000端口,后台又是运行在另一个端口,前后台通信跨域。AJAX跨域一般有两种解决方法:CORS(跨域资源共享)和JSONP。
先来看看JSONP,本质原理利用script标签src属性可以跨域的特性,我们自己也可以去实现JSONP,动态添加删除script标签:
function loadJs() {
var script = document.createElement("script");
script.src = "http://xxxxxx/get/req";
document.body.appendChild(script);
script.onload = function() {
callback();
document.body.removeChild(script);
}
}
在AJAX中使用JSOP:
$.ajax({
url: 'http://xxxxxx/get/req',
cache: false,
type: 'post',
jsonp:'callback',
dataType:'jsonp',
success: function (result) {
//处理结果的过程
},
error: function (XMLHttpRequest) {
//出错回调处理
}
});
相应的后台代码:
/**
* 测试
*
* @throws IOException
*/
@RequestMapping("/get/req")
@ResponseBody
public void getData(HttpServletRequest req, HttpServletResponse rep) throws IOException {
Map<String, Object> result = null;
result = manager.getDatas();
String callback = req.getParameter("callback");
String json = JSONObject.toJSONString(result);
rep.setContentType("text/javascript");
rep.setCharacterEncoding("utf-8");
PrintWriter out = rep.getWriter();
out.print(callback + "(" + json + ")");
}
CORS分为简单请求和非简单请求,非简单请求的请求方法不是POST、GET、HEAD,或者header中包含一些特殊请求头,以及Content-Type不是application/x-www-form-urlencoded、multipart/form-data、text/plain的请求。向后台发送请求时会多发送一次option请求,并且不能携带cookie,这个后面再说。CORS只需服务器端在每次响应中设置一些响应头即可,将这些操作放在Filter中实现低耦合:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse rep = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
rep.addHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
rep.setHeader("Access-Control-Allow-Credentials", "true");//跨域携带cookie
rep.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");//非简单请求
rep.setContentType("application/json; charset=utf-8");
rep.setHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE");//非简单请求
chain.doFilter(request, rep);
}
我们的项目需要接入统一登录中心,SSO大家也知道,每次请求来了之后SSOClient判断是否携带token,过期或者没有token会重定向到登录页面让用户进行登录。这里出现了一个问题,前端向后台发送AJAX请求,在用户没有登录时并不能正常重定向到登录中心,而是出现前台到登录中心页面的XMLHttpRequest跨域错误提示。但是通过浏览器直接输入后台请求URL地址,在用户没有登录时,会重定向到登录中心页面。对于这个问题的产生一开始没有头绪,进行了多种尝试之后定位到了原因。
这里涉及到浏览器是如何处理AJAX请求重定向,当服务器将302响应发给浏览器,浏览器并不是直接进行AJAX回调处理,而是先执行302重定向,从Response Headers中读取Location信息,然后向Location中的Url发出请求,在收到这个请求的响应后才会进行AJAX回调处理。antD是个单页面应用,前后台交互通过AJAX的方式,所以当SSOFilter拦截ajax请求之后,浏览器会首先重定向到登陆中心,产生了前台到登录中心的跨域AJAX请求,当浏览器发现响应头信息没有包含Access-Control-Allow-Origin字段,从而抛出XMLHttpRequest cannot load的错误。
CORS请求默认不发送Cookie和http认证信息,如果要把Cookie发送到服务器,一方面需要服务器同意,指定Access-Control-Allow-Credentials响应头,另一方面必须在AJAX中打开withCredentials属性,$.ajaxSetup方法设置AJAX请求的默认参数选项,多个AJAX请求时,不用为每一个请求配置请求的参数:
$.ajaxSetup({
xhrFields: {
withCredentials: true
},
})