WEB开发过程中最常使用 Ajax技术来完成客户端与服务器的通信。而实现Ajax通信的XmlHttpRequest对象会带来跨域安全策略问题。简单来说,默认情况下,XHR对象只能访问与包含它的页面位于同一个域下的资源。
那么问题来了,何为跨域呢?通常,Ajax指向的地址中,二级域名/端口号/协议/必须与包含它的页面相同。举个栗子:
www.tangide.com 访问 www.i5r.com是跨域。
a.tangide.com 访问 b.tangide.com是跨域。
www.tangide.com:8080 访问 www.tangide.com:8090是跨域。
http://www.tangide.com 访问https://www.tangide.com是跨域。
跨域虽然会带来一些安全性方面的问题,但有时候跨域请求资源也是必要的。下面介绍两个常用的跨域方法。
1)CORS:简称为跨域资源共享
大概的工作原理可以理解为:A上的页面想要获取B上的资源,浏览器会先发送一个HEAD请求获取B服务器的http header 判断Access-Control-Allow-Origin是否有A(根据之前的栗子推测)
如果有则浏览器允许跨域,否则拒绝~
简单写个测试代码,先用Nodejs搭建一个简单的服务器:
var http = require('http');
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type':'text/plain',
'Access-Control-Allow-Origin':'http://www.i5r.com',
'Access-Control-Allow-Headers':'If-Modified-Since'}) ;
console.log((req.url));
console.log((req.headers));
res.end(JSON.stringify({code: 0, message:'ok'}));
});
server.listen(8090);
服务器监听8090端口。Access-Control-Allow-Origin选项列出了服务器允许跨域资源请求的origin。Access-Control-Allow-Headers选项用于配置允许客户端发送的头部信息。If-Modified-Since选项比较常用,主要用于浏览器的缓存。当浏览器发送http请求获取资源的时候If-Modified-Since选项会带上资源文件最后修改的时间(GMT格式),表示如果从这个时间点后该请求的资源有变化就更新资源。否则服务器返回304状态码,表示“Not Modified”。所以如果有一些资源强制浏览器不缓存,可以把If-Modified-Since设置为0,这样每次都从服务器获取一次资源。
接下来编写客户端测试代码:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' Content='type=text/html;chatset=utf-8'>
<script type='text/javascript' src='ajax.js'></script>
<script type="text/javascript">
window.onload = function() {
var button = document.createElement('input');
button.setAttribute('type', 'button');
button.setAttribute('value', 'send request');
document.getElementsByTagName('body')[0].appendChild(button);
button.onclick = function() {
window.handle = Ajax.getInstance();
handle.testAction('/read.php', function(result) {
console.log(JSON.stringify(result));
});
};
};
</script>
</head>
<body>
</body>
</html>
发送Ajax请求的相关代码:
funcntion testAction(action, onDone) {
var info = {};
info.method = 'post';
info.url = 'http://www.i5r.com:8090' + action;
httpSendRequest(info);
}
function httpSendRequest(info) {
var xhr = new window.XMLHttpRequest();
if(!info || !info.url) {
return false;
}
var url = info.url;
var data= info.data;
var method = info.method ? info.method : "GET";
xhr.open(method, url, true);
xhr.send(info.data ? info.data : null);
xhr.onreadystatechange = function() {
if(info.onProgress) {
info.onProgress(xhr);
}
if(xhr.readyState === 4) {
if(info.onDone) {
info.onDone(true, xhr, xhr.responseText);
console.log("response:" + xhr.responseText);
}
}
console.log("onreadystatechange:" + xhr.readyState);
return;
};
服务器虽然监听的是8090端口,但是已经把www.i5r.com添加到了跨域允许列表所以,正常情况下输出如下:
response:{"code":0,"message":"ok"}
onreadystatechange:4
我们可以把'Access-Control-Allow-Origin':'http://www.i5r.com'这句稍微做一点改动,比如默认支持8080端口的跨域请求:'Access-Control-Allow-Origin':'http://www.i5r.com'。刷新页面:点击发送请求,会得到如下错误信息:
XMLHttpRequest cannot load http://www.i5r.com:8090/read.php. The 'Access-Control-Allow-Origin' header has a value 'http://www.i5r.com:8080' that is not equal to the supplied origin. Origin 'http://www.i5r.com' is therefore not allowed access.
错误信息已经说明了原因了~
如果是用express搭建的服务器可以如下配置:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
这里的"*"是一个通配符,表示允许所以的请求源跨域。
2) 使用JSONP完成跨域资源请求,首先编写前端测试代码:
<html>
<head>
<meta http-equiv='Content-Type' Content='type=text/html;chatset=utf-8'>
<script type='text/javascript' src='ajax.js'></script>
<script type="text/javascript">
function handleResponse(res) {
console.log(res);
}
window.onload = function() {
var button = document.createElement('input');
button.setAttribute('type', 'button');
button.setAttribute('value', 'send request');
document.getElementsByTagName('body')[0].appendChild(button);
button.onclick = function() {
var script = document.createElement('script');
script.src = 'http://www.i5r.com:8090?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
};
};
</script>
</head>
<body>
</body>
</html>
处理函数在收到服务器响应后直接打印反馈信息。
修改服务器测试代码:
var http = require('http');
var urlParser = require('url');
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type':'text/plain'});
console.log(req.url);
var query = urlParser.parse(req.url, true).query;
console.log(query);
if(query.callback) {
var str = query.callback + "(" +JSON.stringify({code: 0, message:'ok'})+");";
console.log(str);
res.end(str);
}
else {
res.end(JSON.stringify({code: 0, message:'ok'}));
}
});
server.listen(8090);
前端在发送请求的时候把"回调"函数的名称加到url后面,服务器在处理请求的时候直接返回需要执行回调函数的数据格式,这样浏览器在判断当前页面有同名函数的时候就会执行该函数。这样就完成了一次跨域请求。
最后需要说明的是,限制跨域是浏览器的行为,而不是JS或DOM的行为,如果有能力可以自己开发一个浏览器来完成跨域支持:)。
跨域请求不同方式各自都有利有弊,如果你也在研究类似的问题,欢迎交流~
参考资料:https://github.com/drawapp8/gamebuilder/wiki/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3