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.错误信息已经说明了原因了~
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
原文地址:http://blog.csdn.net/gentlycare/article/details/44138261