AJAX跨域
跨域
通过XHR实现AJAX通信的一个主要限制来源于跨域安全。默认情况下,XHR对象只能访问与包含他的页面位于同一个域中的资源。这样可以预防某些恶意行为,但是实现合理的跨域请求对开发某些应用程序是至关重要的。下面来介绍一些XHR实现跨域的方式。
CORS
CORS实现跨域的思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
在向服务器发送请求时会额外附加一个Origin头部,其中包含请求页面的源信息(协议、域名和端口),服务器可以根据这个头信息来决定是否给予响应。
Origin: http://www.baidu.com
如果服务器认为该请求可以接受,就在Access-Control-Allow-Origin头部中会发相同的源信息或者(*)
Access-Control-Allow-Origin: http://www.baidu.com
如果没有这个头部,或者这个头部信息和源信息不匹配,浏览器就会驳回请求。
NOTE:通过CORS发送的请求和响应都不会包含cookie信息。
1、IE对CORS的实现(IE8、IE9、IE10,IE11+可以通过XHR实现跨域)
IE8+引入了XDR(XDomainRequest)对象,该对象与XHR类似,并且能实现安全可靠的跨域通信。XDR与XHR的不同:
cookie不能发送也不会随响应返回
只能访问请求头和响应头的Content-Type字段(不能设置)
不允许访问响应头部信息
只支持GET和POST请求
只能发送异步请求,不能发送同步请求
XDR在接受到响应后只能访问响应返回的数据,没办法确定响应的状态代码。但是只要响应有效就会触发load事件,失败就会触发error事件。
get请求的XDR示例:
1 header('Access-Control-Allow-Origin:http://localhost:63342'); 2 echo json_encode($_GET);
1 var xdr = new XDomainRequest(); 2 xdr.open('get', 'http://localhost/ajax/data.php?'+fnGetURLParam({name: 'hum', age: 20})); 3 xdr.send(null); 4 xdr.onload = function(){ 5 console.log(xdr.responseText); 6 } 7 xdr.onerror = function(){ 8 console.log('error'); 9 }
post请求的XDR示例:
1 header('Access-Control-Allow-Origin:http://localhost:63342'); 2 echo json_encode($_POST);
1 var xdr = new XDomainRequest(); 2 // xdr.contentType = "application/X-www-form-urlencoded"; // contentType是只读的(IE8报错IE9不报错但是设置不成功) 3 xdr.open('post', 'http://localhost/ajax/data.php'); 4 xdr.send(fnGetURLParam({name: 'hum', age: 20})); 5 xdr.onload = function(){ 6 console.log(xdr.responseText); // [] 7 console.log(xdr.contentType); // text/html 8 } 9 xdr.onerror = function(){ 10 console.log('error'); 11 }
NOTE:XDR的post请求无法设置请求头Content-Type为application/x-www-form-urnencoded,后端不能获取请求数据。为了兼容IE89不能使用POST方式。
XDR对象的其他方法、事件、属性:
abort方法:在响应前调用来中断请求
1 var xdr = new XDomainRequest(); 2 xdr.open('get', 'http://localhost/ajax/data.php?'+fnGetURLParam({name: 'hum', age: 20})); 3 xdr.send(null); 4 xdr.onload = function(){ 5 console.log(xdr.responseText); // 不会执行 6 } 7 xdr.onerror = function(){ 8 console.log('error'); 9 } 10 xdr.abort(); // 中断请求
NOTE:timeout属性、ontimeout事件可能有些问题。考虑使用jquery的定时器调用abort来模拟超时。XHR对象也可。
1 header('Access-Control-Allow-Origin:http://localhost:63342'); 2 sleep(10); 3 echo json_encode($_GET);
1 var xdr = new XDomainRequest(); 2 xdr.open('get', 'http://localhost/ajax/data.php?'+fnGetURLParam({name: 'hum', age: 20})); 3 xdr.send(null); 4 xdr.onload = function(){ 5 console.log(xdr.responseText); //没有输出 6 } 7 xdr.onerror = function(){ 8 console.log('error'); 9 } 10 setTimeout(function(){ 11 xdr.abort(); 12 }, 1000);
2、标准浏览器对CORS的支持
标准浏览器都对XHR对象实现了对CORS的原生支持。
XHR的CORS跨域,XHR对象可以访问status和同步请求,但是也有一些限制:
不能使用setRequestHeader设置自定义头部信息
不能发送和接受cookie
兼容IE8+的CORS封装:
1 // 兼容IE8+ 只能发送get类型的异步跨域请求 2 function fnGetCorsRequest(url, data, fnSuccess, fnError) { 3 var xhr = new XMLHttpRequest; 4 if ('withCredentials' in xhr) { 5 xhr.open('get', url + '?' + fnGetURLParam(data), true); 6 } else { 7 xhr = new XDomainRequest; 8 xhr.open('get', url + '?' + fnGetURLParam(data)); 9 } 10 xhr.send(null); 11 if (fnSuccess) { // 成功 12 xhr.onload = function () { 13 fnSuccess(xhr.responseText); 14 } 15 } 16 if (fnError) { // 失败 17 xhr.onerror = function (e) { 18 fnError(e); 19 } 20 } 21 return xhr; 22 } 23 // 测试 24 var xhr = fnGetCorsRequest( 25 'http://localhost/ajax/data.php', 26 {name: 'hum', age: 20}, 27 function (data) { 28 console.log(data); 29 }, 30 function (e) { 31 console.log(e); 32 } 33 ); 34 35 // 获取URLParam的辅助方法 36 function fnGetURLParam(data) { 37 var urlParam = []; 38 for (var key in data) { 39 urlParam.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); 40 } 41 return urlParam.join('&'); 42 }
IE11+和标准浏览器的CORS跨域封装:
1 // 兼容IE11+和标准浏览器 支持异步和同步 get和post 2 function fnGetCorsRequest(method, url, data, bAsyn, fnSuccess, fnError) { 3 var xhr = new XMLHttpRequest; 4 data = fnGetURLParam(data); 5 if (method == 'get') { 6 url += '?' + data; 7 data = null; 8 } 9 xhr.open(method, url, bAsyn); 10 if(method == 'post'){ 11 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // setRequestHeader要放在open后 12 } 13 xhr.onload = function () { 14 fnSuccess && fnSuccess(xhr.responseText); 15 } 16 xhr.onerror = function (e) { 17 fnError && fnError(e); 18 } 19 xhr.send(data); // 支持同步 send方法应该放在事件绑定后面 20 21 return xhr; 22 } 23 // 测试 24 var xhr = fnGetCorsRequest( 25 'post', 26 'http://localhost/ajax/data.php', 27 {name: 'hum', age: 20}, 28 true, 29 function (data) { 30 console.log(data); 31 }, 32 function (e) { 33 console.log(e); 34 } 35 ); 36 37 // 获取URLParam的辅助方法 38 function fnGetURLParam(data) { 39 var urlParam = []; 40 for (var key in data) { 41 urlParam.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); 42 } 43 return urlParam.join('&'); 44 }
Image对象(img标签)
一个网页可以从任何其他网页加载图片,而不会出现跨域的问题。
1 var oImg = new Image; 2 oImg.src = 'http://blog.smdcn.net/dizxcv.png'; 3 oImg.onload = function(){ 4 document.body.innerHTML += '<img src="' + oImg.src + '" />'; 5 }
缺点:只能get请求,无法访问响应文本......
JSONP
JSONP(JSON with padding)填充式的JSON的缩写,是被包含在函数调用中的JSON。
JSONP由两部分组成:回掉函数和JSON
回调函数是当响应返回时在页面中需要执行的函数
JSONP是通过script标签来使用的,在src中可以制定一个跨域的URL
优点:
可以直接访问响应文本,支持浏览器与服务器双向访问
缺点:
安全性低
只能进行get请求
不能检测请求失败
1 $data = json_encode(array( // php代码 2 'name' => $_GET['name'], 3 'age' => $_GET['age'] 4 )); 5 $callback = $_GET['callback']; 6 echo $callback.'('. $data .')';
1 function fnUseJSONP(url, data, fnCallback) { // js代码 2 var oScript = document.createElement('script'); 3 oScript.type = 'text/javascript'; 4 if (data) { 5 oScript.src = url + '?' + fnGetURLParam(data) + '&callback=!' + fnCallback;// 注意这个!号 6 } else { 7 oScript.src = url + '?callback=!' + fnCallback; // 注意这个!号 8 } 9 // 请求返回!function (data) { console.log(data); }({"name":"hum","age":"20"}) 10 // 为了让该函数自执行可以加上!等 11 document.body.appendChild(oScript); 12 } 13 14 fnUseJSONP( 15 'http://localhost/ajax/data.php', 16 {name: 'hum', age: 20}, 17 function (data) { 18 console.log(data); 19 } 20 ); 21 22 // 获取URLParam的辅助方法 23 function fnGetURLParam(data) { 24 var urlParam = []; 25 for (var key in data) { 26 urlParam.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); 27 } 28 return urlParam.join('&'); 29 }
其他跨域技术
comet、SSE、web sockets有兴趣的同学可以自行研究。。。