zoukankan      html  css  js  c++  java
  • 你不知道的JavaScript--Item33 跨域总结与解决的方法

    一、神马是跨域(Cross Domain)

    说白点就是post、get的url不是你当前的站点,域名不同。比如在*aaa.com/a.html*里面,表单的提交action是bbb.com/b.html

    不仅如此,www.aaa.comaaa.com之间也属于跨域。由于www.aaa.com是二级域名,aaa.com是根域名。

    JavaScript出于安全方面的考虑,是不同意跨域调用其它页面的对象的(同源策略 Same-Origin Policy)。

    这里写图片描写叙述

    特别注意两点:

    • 第一。假设是协议和端口造成的跨域问题“前台”是无能为力的。
    • 第二:在跨域问题上。域仅仅是通过“URL的首部”来识别而不会去尝试推断同样的ip地址相应着两个域或两个域是否在同一个ip上。

      “URL的首部”指window.location.protocol +window.location.host,也能够理解为“Domains, protocols and ports must match”。

    二、为嘛要跨域

    跨域这东西事实上非经常见,比如我们能够把站点的一些脚本、图片或其它资源放到另外一个站点。

    比如我们能够使用Google提供的jQuery,载入时间少了,而且降低了server的流量,例如以下:

    <script type="text/java script" src="https://aja x.googleapis.com/aj ax/libs/jquery/1.4.2/jquery.min.js"></script>

    跨域问题产生的场景

    有时候不仅仅是一些脚本、图片这样的资源,我们也会希望从另外的站点调用一些数据(有时候是不得不这样)。比如我希望获取一些blog的RSS来生成一些内容,再或者说我在“人人开放平台”上开发一个应用。须要调用人人的数据。

    当要在在页面中使用js获取其它站点的数据时。就会产生跨域问题,比方在站点中使用ajax请求其它站点的天气、快递或者其它数据接口时以及hybrid app中请求数据,浏览器就会提示以下错误。

    这样的场景下就要解决js的跨域问题。
    然而,非常不幸的是,直接用XMLHttpRequest来Get或者Post是不行的。比如我用jQuery的$.get去訪问例如以下主域名 :

    $.get("http://flycoder.org/",
    {}, function(data){
    alert('跨域不是越狱:'+data)
    }, "html");

    结果例如以下(总之就是不行啦~FF不报错,可是木有返回数据):

    这里写图片描写叙述

    那咋么办捏?(弱弱的说,測试的时候我发现IE訪问本地文件时,是能够跨域的,只是这也没啥用~囧~)

    三、肿么跨域

    在浏览器中,<script><img><iframe><link>这几个标签是能够载入跨域(非同源)的资源的,而且载入的方式事实上相当于一次普通的GET请求,唯一不同的是,为了安全起见,浏览器不同意这样的方式下对载入到的资源的读写操作,而仅仅能使用标签本身应当具备的能力(比方脚本运行、样式应用等等)。


    最常见的跨域问题是Ajax跨域訪问的问题,默认情况下,跨域的URL是无法通过Ajax訪问的。这里我记录我所了解到的跨域的方法:

    1. server端代理,这没有什么可说的。缺点在于,默认情况下接收Ajax请求的服务端是无法获取到的client的IP和UA的。

    2. iframe,使用iframe事实上相当于开了一个新的网页,详细跨域的方法大致是,域A打开的母页面嵌套一个指向域B的iframe,然后提交数据。完毕之后,B的服务端能够:
      ●返回一个302重定向响应,把结果又一次指回A域;
      ●在此iframe内部再嵌套一个指向A域的iframe。

    这两者都终于实现了跨域的调用。这种方法功能上要比以下介绍到的JSONP更强。由于跨域完毕之后DOM操作和互相之间的JavaScript调用都是没有问题的。可是也有一些限制,比方结果要以URL參数传递,这就意味着在结果数据量非常大的时候须要切割传递,甚是麻烦;另一个麻烦是iframe本身带来的。母页面和iframe本身的交互本身就有安全性限制。

    3、 利用script标签跨域,这个办法也非经常见。script标签是能够载入异域的JavaScript并运行的,通过预先设定好的callback函数来实现和母页面的交互。它有一个大名。叫做JSONP跨域。JSONP是JSON with Padding的略称。

    它是一个非官方的协议,明明是载入script,为啥和JSON扯上关系呢?原来就是这个callback函数。对它的使用有一个典型的方式,就是通过JSON来传參,即将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。以下详细介绍一下。

    为了更好的解说和測试,我们能够通过改动hosts文件来模拟跨域的效果。hosts文件在C:WindowsSystem32driversetc 目录下。在以下加3行:

    127.0.0.1 www.a.com
    
    127.0.0.1 a.com
    
    127.0.0.1 www.b.com

    3.1、跨域代理

    一种简单的办法。就是把跨域的工作交给server。从后台获取其它站点的数据再返回给前台,也就是跨域代理(Cross Domain Proxy)。

    这样的方法似乎蛮简单的。改动也不太大。只是就是http请求多了些,响应慢了些。server的负载重了些~

    这里写图片描写叙述

    3.2、document.domain+iframe的设置

    对于主域同样而子域不同的样例,能够通过设置document.domain的办法来解决。

    举www.a.com/a.html和a.com/b.html为例.

    思路:仅仅需在a.html中加入一个b.html的iframe,而且设置两个页面的document.domain都为’a.com’(仅仅能为主域名)。两个页面之间即可互相訪问了。代码例如以下:

    www.a.com/a.html中的script

    <!DOCTYPE HTML>
    <html>
    <head>
        <meta name="name" content="content" charset="utf-8">
    </head>
    <body>
    <script type="text/javascript">
        document.domain='a.com';
        var ifr = document.createElement('iframe');
        ifr.src = 'http://a.com/b.html';
        ifr.style.display = 'none';
        document.body.appendChild(ifr);
        ifr.onload = function(){
          //获取iframe的document对象
          //W3C的标准方法是iframe.contentDocument,
          //IE6、7能够使用document.frames[ID].document
          //为了更好兼容,可先获取iframe的window对象iframe.contentWindow
          var doc = ifr.contentDocument || ifr.contentWindow.document;
          // 在这里操纵b.html
          alert(doc.getElementById("test").innerHTML);
       };
    </script>
    </body>
    </html>

    备注:某一页面的domain默认等于window.location.hostname。

    主域名是不带www的域名,比如a.com。主域名前面带前缀的通常都为二级域名或多级域名,比如www.a.com事实上是二级域名。

    domain仅仅能设置为主域名。不能够在b.a.com中将domain设置为c.a.com。

    a.com/b.html

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <title></title>
    <script type="text/javascript">
      document.domain='a.com';
    </script>
    </head>
    <body>
    <h1 id="test">Hello World</h1>
    </body>
    </html>

    这里写图片描写叙述

    假设b.html要訪问a.html,可在子窗体(iframe)中通过window.parent来訪问父窗体的window对象,然后就能够为所欲为了(window对象都有了,还有啥不行的),同理子窗体也能够和子窗体之间通信。

    于是,我们能够通过b.html的XMLHttpRequest来获取数据,再传给a.html。从而解决跨子域获取数据的问题。

    可是这样的方法仅仅支持同一根域名下的页面。假设不同根域名(比如baidu.com想訪问google.com)那就无能为力了。

    问题:

    • 1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。

    • 2、假设一个页面中引入多个iframe,要想能够操作全部iframe,必须都得设置同样domain。

    3.3、动态script标签(Dynamic Script Tag)

    尽管浏览器默认禁止了跨域訪问。但并不禁止在页面中引用其它域的JS文件,并能够自由运行引入的JS文件里的function(包含操作cookie、Dom等等)。依据这一点。能够方便地通过创建script节点的方法来实现全然跨域的通信。

    这样的方法也叫“动态脚本注入”。

    这样的技术克服了XMLHttpRequest的最大限制,也就是跨域请求数据。直接用JavaScript创建一个新的脚本标签,然后设置它的src属性为不同域的URL。

    www.a.com/a.html中的script

    var dynScript = document.createElement('script');
    dynScript.src = 'http://www.b.com/b.js';
    dynScript.setAttribute("type", "text/javascript");
    document.getElementsByTagName('head')[0].appendChild(dynScript);

    通过动态标签注入的必须是可运行的JavaScript代码,因此不管是你的数据格式是啥(xml、json等),都必须封装在一个回调函数中。一个回调函数例如以下:

    www.a.com/a.html中的script

    function dynCallback(data){
      //处理数据, 此处简单示意一下
      alert(data.content);
    }

    在这个样例中。www.b.com/b.js须要将数据封装在上面这个dynCallback函数中。例如以下:

    dynCallback({content:'来自b.com/b.js的消息Hello World!'});

    我们看到了让人开心的结果。Hello World~

    这里写图片描写叙述

    只是动态脚本注入还是存在不少问题的,以下我们拿它和XMLHttpRequest来对照一下:

    这里写图片描写叙述

    能够看出,动态脚本注入还是有不少限制,仅仅能使用Get。不能像XHR一样推断Http状态等。

    而且使用动态脚本注入的时候必须注意安全问题。由于JavaScript没有不论什么权限与訪问控制的概念。通过动态脚本注入的代码能够全然控制整个页面。所以引入外部来源的代码必须多加小心。

    3.4 利用iframe和location.hash

    这个办法比較绕,可是能够解决全然跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。

    • ww.a.com下的a.html想和www.b.com下的b.html通信(在a.html中动态创建一个b.html的iframe来发送请求)。

    • 可是由于“同源策略”的限制他们无法进行交流(b.html无法返回数据),于是就找个中间人:www.a.com下的c.html(注意是www.a.com下的);

    • b.html将数据传给c.html(b.html中创建c.html的iframe)。由于c.html和a.html同源,于是可通过c.html将返回的数据传回给a.html。从而达到跨域的效果。

    这里写图片描写叙述

    三个页面之间传递參数用的是location.hash(也就是www.a.html#sayHello后面的’#sayHello’)。改变hash并不会导致页面刷新(这点非常重要)。

    详细代码例如以下:

    www.a.com/a.html

    //通过动态创建iframe的hash发送请求
    function sendRequest(){
      var ifr = document.createElement('iframe');
      ifr.style.display = 'none';
      //跨域发送请求给b.html, 參数是sayHello
      ifr.src = 'http://www.b.com/b.html#sayHello';
      document.body.appendChild(ifr);
    }
    //获取返回值的方法
    function checkHash() {
      var data = location.hash ?

    location.hash.substring(1) : ''; if (data) { //处理返回值 alert(data); location.hash=''; } } //定时检查自己的hash值 setInterval(checkHash, 2000); window.onload = sendRequest;

    www.b.com/b.html

    function checkHash(){
      var data = '';
      //模拟一个简单的參数处理操作
      switch(location.hash){
        case '#sayHello': data = 'HelloWorld';break;
        case '#sayHi': data = 'HiWorld';break;
        default: break;
      }
      data && callBack('#'+data);
    }
    function callBack(hash){
      // ie、chrome的安全机制无法改动parent.location.hash,
      // 所以要利用一个中间的www.a.com域下的代理iframe
      var proxy = document.createElement('iframe');
      proxy.style.display = 'none';
      // 注意该文件在"www.a.com"域下
      proxy.src = 'http://www.a.com/c.html'+hash;
      document.body.appendChild(proxy);
    }
    window.onload = checkHash;

    www.a.com/c.html

    //由于c.html和a.html属于同一个域。
    //所以能够改变其location.hash的值
    //可通过parent.parent获取a.html的window对象
    parent.parent.location.hash = self.location.hash.substring(1);

    这里写图片描写叙述

    可能有人会有疑问。既然c.html已经获取了a.html的window对象了。为何不直接改动它的dom或者传递參数给某个变量呢?

    原因是在c.html中改动 a.html的dom或者变量会导致页面的刷新。a.html会又一次訪问一次b.html,b.html又会訪问c.html。造成死循环……囧呀~

    所以仅仅能通过location.hash了。这样做也有些不好的地方,诸如数据容量是有限的(受url长度的限制),而且数据暴露在url中(用户能够任意改动)……

    3.5、postMessage(html5)

    HTML5中最酷的新功能之中的一个就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。

    Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

    otherWindow.postMessage(message, targetOrigin);

    • otherWindow: 对接收信息页面的window的引用。能够是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。

    • message: 所要发送的数据,string类型。
    • targetOrigin: 用于限制otherWindow,“*”表示不作限制
    • a.com/index.html中的代码:
    
    <iframe id="ifr" src="http://www.b.com/b.html"></iframe>
    <script>
    window.onload = function() {
        var ifr = document.getElementById('ifr');
        // 若写成'http://www.c.com'就不会运行postMessage了
        var targetOrigin = 'http://www.b.com';
        ifr.contentWindow.postMessage('sayHello', targetOrigin);
    };

    b.com/b.html中的代码:

    //通过message事件来通信,实在太爽了
    window.addEventListener('message', function(e){
      // 通过origin属性推断消息来源地址
      if (e.origin == 'http://www.a.com' &&
        e.data=='sayHello') {
        alert('Hello World');
      }
    }, false);

    这里写图片描写叙述

    3.5 使用window.name来进行跨域

    window对象有个name属性。该属性有个特征:即在一个窗体(window)的生命周期内,窗体载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限。window.name是持久存在一个窗体载入过的全部页面中的,并不会因新页面的载入而进行重置。

    比方:有一个页面www.a.com/a.html它里面有这样的代码:

    <script type="text/javascript">
        window.name = "我是a.html的window.name";
        setTimeout(function(){
            window.location = 'b.html';
        },3000);

    再看看www.a.com/b.html页面的代码:

    <script type="text/javascript">
    alert(window.name);
    </script>

    这里写图片描写叙述

    我们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。假设在之后全部载入的页面都没对window.name进行改动的话,那么全部这些页面获取到的window.name的值都是a.html页面设置的那个值。

    当然,假设有须要,其中的不论什么一个页面都能够对window.name的值进行改动。

    注意,window.name的值仅仅能是字符串的形式。这个字符串的大小最大能同意2M左右甚至更大的一个容量。详细取决于不同的浏览器,但通常是够用了。

    上面的样例中。我们用到的页面a.html和b.html是处于同一个域的,可是即使a.html与b.html处于不同的域中。上述结论同样是适用的,这也正是利用window.name进行跨域的原理。

    以下就来看一看详细是怎么样通过window.name来跨域获取数据的。还是举例说明。

    比方有一个www.a.com/a.html页面,须要通过a.html页面里的js来获取另一个位于不同域上的页面www.b.com/b.html里的数据。

    b.html页面里的代码非常easy,就是给当前的window.name设置一个a.html页面想要得到的数据值。b.html里的代码:

    <script type="text/javascript">
      window.name ="我就是页面a.html想要的数据,全部能够转化成字符串的数据都能够在这里使用。比方一个json数据";
    </script>

    那么在a.html页面中,我们怎么把data.html页面载入进来呢?显然我们不能直接在a.html页面中通过改变window.location来载入data.html页面,由于我们想要即使a.html页面不跳转也能得到data.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

    充其中间人的iframe想要获取到data.html的通过window.name设置的数据。仅仅须要把这个iframe的src设为www.cnblogs.com/data.html即可了。然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值。还必须把这个iframe的src设成跟a.html页面同一个域才行,不然依据前面讲的同源策略,a.html是不能訪问到iframe里的window.name属性的。这就是整个跨域过程。

    看下a.html页面的代码:

    <script type="text/javascript">
    function getData(){
        var ifr = document.getElementById('proxy');
        ifr.onload = function(){//这个时候a.html与ifr已经是同源了,能够相互訪问
            var data= ifr.contentWindow.name;//获取iframe里的数据,也就是data.html页面设置的数据
            alert(data);//成功获得了数据。
        }
        ifr.src='about:blank';//这里的about:blank能够是随便的一个页面,仅仅要与a.html同源就能够,目的是让a.html能够訪问到iframe里的数据。

    } </script> <iframe id="proxy" src="http://www.b.com/b.html" style="display: none" onload="getData()"></iframe>

    这里写图片描写叙述

    上面的代码仅仅是最简单的原理演示代码。你能够对使用js封装上面的过程,比方动态的创建iframe,动态的注冊各种事件等等,当然为了安全,获取完数据后,还能够销毁作为代理的iframe。网上也有非常多相似的现成代码。有兴趣的能够去找一下。

    通过window.name来进行跨域,就是这样子的。

    3.6 通过jsonp跨域

    在js中,我们直接用XMLHttpRequest请求不同域上的数据时。是不能够的。可是,在页面上引入不同域上的js脚本文件却是能够的,jsonp正是利用这个特性来实现的。

    json≠jsonp

    原理
    jsonp解决跨域问题的原理是,浏览器的script标签是不受同源策略限制(你能够在你的网页中设置script的src属性问cdnserver中静态文件的路径)。那么就能够使用script标签从server获取数据,请求时加入一个參数为callbakc=?,?号时你要运行的回调方法。
    比方,有个www.a.com/a.html页面。它里面的代码须要利用ajax获取一个不同域上的json数据。假设这个json数据地址是http://www.b.com/b.php,那么a.html中的代码就能够这样:

    <script type="text/javascript">
    function dosomething(jsondata){
        //处理json数据
    }
    </script>
    <script src="http://www.b.com/b.php?callback=dosomething"></script>

    我们看到获取数据的地址后面另一个callback參数,按惯例是用这个參数名,可是你用其它的也一样。

    当然假设获取数据的jsonp地址页面不是你自己能控制的,就得依照提供数据的那一方的规定格式来操作了。

    由于是当做一个js文件来引入的。所以http://www.b.com/b.php返回的必须是一个能运行的js文件,所以这个页面的php代码可能是这样的:

    <?php
    $callback = $_GET['callback'];//得到回调函数
    $data = array('a','b','c','d');//要返回的数据
    echo $callback.'('.json_encode($data).')';//输出
    ?>

    终于那个页面输出的结果是:

    这里写图片描写叙述

    所以通过http://www.b.com/b.php?callback=dosomething得到的js文件,就是我们之前定义的dosomething函数,而且它的參数就是我们须要的json数据,这样我们就跨域获得了我们须要的数据。

    这样jsonp的原理就非常清楚了,通过script标签引入一个js文件,这个js文件载入成功后会运行我们在url參数中指定的函数,而且会把我们须要的json数据作为參数传入。所以jsonp是须要server端的页面进行相应的配合的。

    知道jsonp跨域的原理后我们就能够用js动态生成script标签来进行跨域操作了,而不用特意的手动的书写那些script标签。假设你的页面使用jquery,那么通过它封装的方法就能非常方便的来进行jsonp操作了。

    <script type="text/javascript">
    $getJSON('http://www.b.com/b.php?callback=?',function(jsondata){
        //处理获得的json数据;
    });

    原理是一样的,仅仅只是我们不须要手动的插入script标签以及定义回掉函数。

    jquery会自己主动生成一个全局函数来替换callback=?

    中的问号。之后获取到数据后又会自己主动销毁,实际上就是起一个暂时代理函数的作用。$.getJSON方法会自己主动推断是否跨域。不跨域的话。就调用普通的ajax方法。跨域的话,则会以异步载入js文件的形式来调用jsonp的回调函数。

    四、总结

    研究了几天,尽管对多种跨域方法都有所了解了,可是真要投入应用还是明显不够的(还是须要借助一些js库)。

    每种方法都有其优缺点,使用的时候事实上应该将多种跨域方法进一步封装一下,统一调用的接口,利用js来自己主动推断哪种方法更为适用 。

  • 相关阅读:
    luoguP2657 [SCOI2009] windy 数 数位dp
    LOJ#3280. 「JOISC 2020 Day4」首都城市 点分治+BFS
    luoguP2168 [NOI2015]荷马史诗 哈夫曼树
    转载-如何在博客园随笔中增加章节导航
    转载-MySQL之终端(Terminal)管理数据库、数据表、数据的基本操作
    转载-MySQL之终端(Terminal)管理MySQL
    数据库缓存
    独立图片服务器的部署(了解)
    CDN加速
    MySQL update替换字段部分内容
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/7081166.html
Copyright © 2011-2022 走看看