首页 > 上网技巧 > js实现跨域的各种方法

js实现跨域的各种方法

时间:2017-08-20 15:03 作者:QQ地带 我要评论

一、浏览器同源策略
 
首先我们需要了解一下浏览器的同源策略,关于同源策略可以仔细看看知乎上的一个解释。传送门
 
总之:同协议,domain(或ip),同端口视为同一个域,一个域内的脚本仅仅具有本域内的权限,可以理解为本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。
 
( 现代浏览器在安全性和可用性之间选择了一个平衡点。在遵循同源策略的基础上,选择性地为同源策略"开放了后门"。 例如img script style等标签,都允许垮域引用资源。)
 
下表给出了相对 http://store.company.com/dir/page.html 同源检测的示例:
 
URL 结果 原因
http://store.company.com/dir2/other.html 成功  
http://store.company.com/dir/inner/another.html 成功  
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同(默认80)
http://news.company.com/dir/other.html 失败 主机名不同
 
 
 
 
 
 
 
 
 
 
 
 
由于浏览器同源策略的限制,让我们无法通过js直接在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架(iframe)中的数据。
 
二、jsonp实现跨域请求数据
 
在javascript中,我们不能直接用ajax请求不同域上的数据。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
 
本地测试利用PHP内置了的Web 服务器来模拟两个端口不同的两个域,现在在web_test目录下有9000和9001两个目录,分别进入两个目录执行
 
web_test/9000: php -S 127.0.0.1:9000
web_test/9001: php -S 127.0.0.1:9001
执行后:
 

 
 
这时候开启了两个本地不同端口的服务器,现在在两个目录下的文件就是在两个不同域。
 
 在9001目录下jsonp_test.html中
 
  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.     <title>jsonp-test</title> 
  5. </head> 
  6. <body> 
  7.     <script type="text/javascript"> 
  8.         function callback_data (data) { 
  9.             console.log(data); 
  10.         } 
  11.     </script> 
  12.     <script type="text/javascript" src="http://127.0.0.1:9000/jsonp.php?callback=callback_data"></script> 
  13. </body> 
  14. </html> 
 
可以看到我们在向9000目录下的jsonp.php文件获取数据时,地址后面跟了一个callback参数(一般的就是用callback这个参数名,你也可以用其他的参数名代替)。
 
如果你要获取数据的页面是你不能控制的,那你只能根据它所提供的接口格式进行获取。
 
因为我们的type规定是当成是一个javascript文件来引入的,所以php文件返回的应该是一个可执行的js文件。
 
 在9000目录下jsonp.php中
 
 
  1. <?php 
  2.     $callback = $_GET['callback'];  // 获取回调函数名 
  3.     $arr = array("name" => "alsy""age" => "20"); // 要请求的数据 
  4.     echo $callback."(". json_encode($arr) .");"// 输出 
  5. ?> 
页面输出就是这样的:
 
callback_data({"name":"alsy","age":"20"}); //执行url参数中指定的函数,同时把我们需要的json数据作为参数传入
 这样我们浏览器中输入http://127.0.0.1:9001/jsonp_test.html,控制台打印出:
 
 
 
这样我们就获取到不同域中返回的数据了,同时jsonp的原理也就清楚了:
 
通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,同时把我们需要的json数据作为参数传入。所以jsonp是需要服务器端和客户端相互配合的。
 
知道jsonp跨域的原理后我们就可以用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。比如jQuery封装的方法就能很方便的来进行jsonp操作了。
 
9001目录下的html中:
 
 
  1. //$.getJSON()方法跨域请求 
  2. $.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?"function(data){ 
  3.     console.log(data); 
  4. }); 
原理是一样的,只不过我们不需要手动的插入script标签以及定义回掉函数。jQuery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。
 
从请求的url和响应的数据就可以很明显的看出来了:
 
 
 

 
 
这里的 jQuery214036133305518887937_1462698255551 就是一个临时代理函数。
 
$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。
 
另外jsonp是无法post数据的,尽管jQuery.getJSON(url, [data], [callback]); 提供data参数让你可以携带数据发起请求,但这样是以get方式传递的。比如:
 
9001目录下的html中:
 
 
  1. //$.getJSON()方法 
  2. $.getJSON("http://127.0.0.1:9000/jsonp.php?callback=?", {u:'abc', p: '123'}, function(jsonData){ 
  3.     console.log(jsonData); 
  4. }); 
 
 
或者是调用$.ajax()方法指定type为post,它还是会转成get方式请求。
 
9001目录下的html中:
 
 
  1. $.ajax({ 
  2.     type: 'post', 
  3.     url: "http://127.0.0.1:9000/jsonp_post.php", 
  4.     crossDomain: true, 
  5.     data: {u: 'alsy', age: 20}, 
  6.     dataType: "jsonp", 
  7.     success: function(r){ 
  8.         console.log(r); 
  9.     } 
  10. }); 
 
 
 
以get形式的话是可以携带少量数据,但是数据量一大就不行了。
 
如果想post大量数据,就可以尝试用CORS(跨域资源共享,Cross-Origin Resource Sharing)。传送门
 
CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX 请求。实现此功能非常简单,只需由服务器发送一个响应标头即可。
 
即服务器响应头设置
 
    header('Access-Control-Allow-Origin: *'); // "*"号表示允许任何域向服务器端提交请求;也可以设置指定的域名,那么就允许来自这个域的请求:
    header('Access-Control-Allow-Methods: POST');
    header('Access-Control-Max-Age: 1000');
比如:
 
9001目录下的一个html文件:
 
  1. $.ajax({ 
  2.     type: 'post'
  3.     url: "http://127.0.0.1:9000/jsonp_post.php"
  4.     crossDomain: true
  5.     data: {u: 'alsy', age: 20}, 
  6.     dataType: "json"
  7.     success: function(r){ 
  8.         console.log(r); 
  9.     } 
  10. }); 
 
9000目录下的php文件:
 
 
  1. <?php 
  2.     header('Access-Control-Allow-Origin: *'); 
  3.     header('Access-Control-Allow-Methods: POST'); 
  4.     header('Access-Control-Max-Age: 1000'); 
  5.     if($_POST){ 
  6.         $arr = array('name' => $_POST['u'], 'age' => $_POST['age']); 
  7.         echo json_encode($arr); 
  8.     } else { 
  9.         echo json_encode([]); 
  10.     } 
  11. ?> 
 
浏览器显示:
 
 
 

 
 
这样也是可以实现跨域post数据的。
 
兼容性。CORS是W3C中一项较新的方案,所以部分浏览器还没有对其进行支持或者完美支持,详情可移至 http://www.w3.org/TR/cors/。
安全问题。CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,如果你需要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障。
三、iframe+window.domain 实现跨子域
 
 现在9001目录下有一个iframe-domain.html文件:
 
复制代码
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domain</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-domain.html</h1>
<script>  
    function test(){
        var obj = document.getElementById("iframe").contentWindow; //获取window对象
        console.log(obj);
    }
</script>
<iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()"></iframe>
</body>
</html>
复制代码
在这个页面中存在一个不同域的框架(iframe),而iframe载入的页面是和目标域在同一个域的,是能够向目标域发起ajax请求获取数据的。
 
那么就想能不能控制这个iframe让它去发起ajax请求,但在同源策略下,不同域的框架之间也是不能够进行js的交互。
 
虽然不同的框架之间(父子或同辈),是能够获取到彼此的window对象的,但是却不能获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器比如ie6也可以使用top、parent等少数几个属性),总之就是获取到了一个无用的window对象。
 
在这个时候,document.domain就派上用场了,我们只要两个域的页面的document.domain设置成一样了,这个例子中由于端口不同,两边的document.domain也要重新设置成"127.0.0.1",才能正常通信。
 
要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,主域必须相同。
 
另举例:a.b.example.com 中某个文档的document.domain 可以设成a.b.example.com、b.example.com 、example.com中的任意一个;
 
但是不可以设成c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。
 
通过设置document.domain为相同,现在已经能够控制iframe载入的页面进行ajax操作了。
 
9001目录下的iframe-domain.html文件改为:
 
复制代码
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>127.0.0.1:9001 -- iframe-domain</title>
</head>
<body>
<h1>127.0.0.1:9001/iframe-domain.html</h1>
<script>
    document.domain = '127.0.0.1'; //设置domain
    function test(){
        var obj = document.getElementById("iframe").contentWindow;
        console.log(obj);
        obj.getData('http://127.0.0.1:9000/json_domain.php', '{u: "alsy-domain", age: "20"}', function(r){
            console.log(  eval("("+ r +")") );
        });
    }
</script>
<iframe id="iframe" src="http://127.0.0.1:9000/domain.html" onload="test()"></iframe>
</body>
</html>
复制代码
 
 
9000目录下有一个domain.html,和一个json_domain.php文件:
 
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>domain</title>
</head>
<body>
<h1>127.0.0.1/9000/domain.html</h1>
<script>
    window.onload = function(){
        document.domain = '127.0.0.1'; //设置domain
        window.getData =  function(url, data, cb) {
            var xhr = null;
            if (window.XMLHttpRequest) {
                xhr = new XMLHttpRequest();
            } else {
                xhr = new ActiveXObject("Microsoft.XMLHTTP");
            }
            xhr.open('POST', url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    cb(xhr.responseText);
                }
            }
            xhr.send(data);
        }
    }
</script>
</body>
</html>
复制代码
<?php
    $str = file_get_contents('php://input');
    echo json_encode($str);
?>
 
 
 
 
这样就可以实现跨域,当然你也可以动态创建这么一个iframe,获取完数据后再销毁。
 
四、iframe+window.name 实现跨域
 
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限。(window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,一般够用了。)
 
9001目录下的iframe-window.name.html:
 
  1. <!doctype html> 
  2. <html> 
  3. <head> 
  4.     <meta charset="UTF-8"> 
  5.     <title>127.0.0.1:9001 -- iframe-window.name</title> 
  6. </head> 
  7. <body> 
  8. <h1>127.0.0.1:9001/iframe-window.name.html</h1> 
  9. <script> 
  10.     function test(){ 
  11.         var obj = document.getElementById("iframe"); 
  12.         obj.onload = function(){ 
  13.             var message = obj.contentWindow.name; 
  14.             console.log(message); 
  15.         } 
  16.         obj.src = "http://127.0.0.1:9001/a.html"
  17.     } 
  18. </script> 
  19. <iframe id="iframe" src="http://127.0.0.1:9000/window.name.html" onload="test()"></iframe> 
  20. </body> 
  21. </html> 
 
9000目录下的window.name.html:
 
 
  1. <!DOCTYPE html> 
  2. <html lang="en"> 
  3. <head> 
  4.     <meta charset="UTF-8"> 
  5.     <title>window.name</title> 
  6. </head> 
  7. <body> 
  8. <h1>127.0.0.1/9000/window.name.html</h1> 
  9. <script> 
  10.     //todo 
  11.     window.name = "This is message!"
  12. </script> 
  13. </body> 
  14. </html> 
 
浏览器输出:
 
 
 
整个跨域的流程就是:现在9000/window.name.html中通过一些操作将数据存入window.name中了,而9001/iframe-window.name.html想要获取到window.name的值就需要依靠iframe作为中间代理,首先把iframe的src设置成http://127.0.0.1:9000/window.name.html,这样就相当于要获取iframe的window.name,而要想获取到iframe中的window.name,就需要把iframe的src设置成当前域的一个页面地址"http://127.0.0.1:9001/a.html",不然根据前面讲的同源策略,window.name.html是不能访问到iframe里的window.name属性的。
 
五、iframe+window.postMessage 实现跨域
 
html5炫酷的API之一:跨文档消息传输。高级浏览器Internet Explorer 8+, chromeFirefox , Opera  和 Safari 都将支持这个功能。这个功能实现也非常简单主要包括接受信息的"message"事件和发送消息的"postMessage"方法。
 
发送消息的"postMessage"方法:
 
向外界窗口发送消息:
 
otherWindow.postMessage(message, targetOrigin);
otherWindow:  指目标窗口,也就是给哪个window发消息。
 
message: 要发送的消息,类型为 String、Object (IE8、9 不支持)
 
targetOrigin: 是限定消息接收范围,不限制请使用 '*'
 
接受信息的"message"事件
 
  1. var onmessage = function (event) { 
  2.     var data = event.data; 
  3.     var origin = event.origin; 
  4.     //do someing 
  5. }; 
  6. if (typeof window.addEventListener != 'undefined') { 
  7.     window.addEventListener('message', onmessage, false); 
  8. else if (typeof window.attachEvent != 'undefined') { 
  9.     //for ie 
  10.     window.attachEvent('onmessage', onmessage); 
 
回调函数第一个参数接收 event 对象,有三个常用属性:
 
data:  消息
origin:  消息来源地址
source:  源 DOMWindow 对象
 9000目录下的 postmessage.html:
 
  1. <!DOCTYPE html> 
  2. <html lang="en"> 
  3. <head> 
  4.     <meta charset="UTF-8"> 
  5.     <title>postmessage</title> 
  6. </head> 
  7. <body> 
  8. <h1>127.0.0.1/9000/postmessage.html</h1> 
  9. <script> 
  10.     window.onload = function () { 
  11.         if (typeof window.postMessage === undefined) { 
  12.             alert("浏览器不支持postMessage!"); 
  13.         } else { 
  14.             window.top.postMessage({u: "alsy"}, "http://127.0.0.1:9001"); 
  15.         } 
  16.     } 
  17. </script> 
  18. </body> 
  19. </html> 
 
 9001目录下的 iframe-postmessage.html:
 
 
  1. <!doctype html> 
  2. <html> 
  3. <head> 
  4.     <meta charset="UTF-8"> 
  5.     <title>127.0.0.1:9001 -- iframe-postmessage</title> 
  6. </head> 
  7. <body> 
  8. <h1>127.0.0.1:9001/iframe-postmessage.html</h1> 
  9. <script> 
  10.     function test(){ 
  11.         if (typeof window.postMessage === undefined) { 
  12.             alert("浏览器不支持postMessage!"); 
  13.         } else { 
  14.             window.addEventListener("message", function(e){ 
  15.                 if (e.origin == "http://127.0.0.1:9000") { //只接收指定的源发来的消息 
  16.                     console.log(e.data); 
  17.                 }; 
  18.             }, false); 
  19.         } 
  20.     } 
  21. </script> 
  22. <iframe id="iframe" src="http://127.0.0.1:9000/postmessage.html" onload="test()"></iframe> 
  23. </body> 
  24. </html> 
 
浏览器打印:
 

标签: JS
顶一下
(0)
0%
踩一下
(0)
0%

Google提供的广告