【跨域】JSONP/CORS/降域/postMessage

浏览器的同源策略

浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

本域指的是?

如:

http://zeeliu.com/a/b.js 和 http://zeeliu.com/index.php (同源)

不同源的例子:

http://zeeliu.com/main.js 和 https://zeeliu.com/a.php (协议不同)
http://zeeliu.com/main.js 和 http://bbs.zeeliu.com/a.php (域名不同,域名必须完全相同才可以)
http://zeeliu.com/main.js 和 http://zeeliu.com:8080/a.php (端口不同,第一个是80)

四中跨域方法

JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。

html中script标签可以引入其他域下的js,比如引入线上的jquery库。利用这个特性,可实现跨域访问接口。需要后端支持

  • 定义数据处理函数:_fn
  • 创建script标签,src的地址执行后端接口,最后加个参数callback=_fun
  • 服务端在收到请求后,解析参数,计算返还数据,输出 fun(data) 字符串。
  • fun(data)会放到script标签做为js执行。此时会调用fun函数,将data做为参数。

代码如下

index.html代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>jsonp跨域</title>
  <style>
    .container {
      width: 500px;
      margin: 0 auto;
    }

  </style>
</head>
<body>
  <div class="container">
    <ul class="news">
      <li>海军首批空中女战勤加入战斗序列</li>
      <li>运20完成首次人员空运试验:乘客均为研发团队成员</li>
      <li>英媒称中国将引领科技变革:新四大发明走向全球</li>        
    </ul>
    <button class="change">换一组</button>
  </div>

  <script>
  
    $('.change').addEventListener('click', function(){
      //点击按钮后创建一个<script>标签;且标签的src=“http://127.0.0.1:8080/getNews?callback=appendHtml”
      var script = document.createElement('script')
      script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
      document.head.appendChild(script) //把这个标签插入头部;就会发送src请求
      document.head.removeChild(script) //发送请求后,这个script标签就没用了,就立即删除;
    })
    
    //由于发送的请求里面有关键字callback=appendHtml
    //所以当后台处理请求时候会把数据用这个关键词和括号链接如:“appendHtml(数据)”
    //下面这个函数就是用来解析数据的;
    function appendHtml(news){
      var html = ''
      for(var i=0; i<news.length; i++){
        html += '<li>' + news[i] + '</li>'
      }
      console.log(html)
      $('.news').innerHTML = html
    }
    
    //封装的document选择器
    function $(selector){
      return document.querySelector(selector)
    }

  </script>

</body>
</html>

router.js的代码

// 注意这里的代码要放在单独的 router.js 文件中


router.get('/getNews', function (req, res) {

  var news = [
    "海军首批空中女战勤加入战斗序列",
    "运20完成首次人员空运试验:乘客均为研发团队成员",
    "英媒称中国将引领科技变革:新四大发明走向全球",
    "印巴军队交火未平息 两国代表联合国又上演舌战",
    "朝鲜的这样东西比“地震”更加震动美国人",
    "郑家概履新武警部队参谋长 接替秦天",
    "从仕途起点清除遗毒 这个落马副部问题多严重?",
    "蔡英文'朴实'午宴曝光:等于退休人员全家4天菜钱"
  ]

  var data = []
  for (var i = 0; i < 3; i++) {
    var index = parseInt(Math.random() * news.length)
    data.push(news[index])
    news.splice(index, 1) //为了避免随机到重复元素,所以push完一个就删除当前
  }

  var cb = req.query.callback
  if (cb) {
    res.send(cb + '(' + JSON.stringify(data) + ')')
  } else {
    res.send(data)
  }
})

上面index.html中<script src="http://127.0.0.1:8080/getNews?callback=appendHtml">标签的src就是一个请求
把这串地址在浏览器打开可以看到返回的数据如下图:

WX20170924-220702.png

其实其他域名地址下的js就是利用这调串数据调用写好的js函数实现解析数据;实现跨域。


CORS

参考文章:阮一峰-跨域资源共享 CORS 详解

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
本文详细介绍CORS的内部机制。

简介

CORS需要浏览器和服务器同时支持。
目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

基本流程

就是说当我发送请求浏览器会默认帮请求添加一个请求头如图下所示
(这都是由浏览器自己完成)

WX20170924-220929.png

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

因此只要后端代码里面同意着了来源的网站请求数据就可以了!
后代码只要在原来的基础上加一个响应头表示我同意这个来源的网站向我请求数据(如下图)

WX20170924-221005.png

此时http://api.bob.com这个网站就可以跨域向这个后端请求数据了

CORS与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。


降域

由于第一次看视频没看懂;最终原因是里面新出现的标签不了解这里顺便把新的知识点也记录一下;

首先什么是降域?

首先我们知道浏览器会阻止两个不同的域名之间进行数据访问操作;
但是当我有一个域名为:zeeliu.com 还有两个子域名:a.zeeliu.com和b.zeeliu.com
由于浏览器的特性a.zeeliu.com和b.zeeliu.com之间也是不能互相访问数据的(同一个爸爸也没用)
但是现实情况是我们希望这两个域名之间互相访问
所以就有了一个`document.domain`的api

例如:

a.zeeliu.com下的文件问index.html
b.zeeliu.com下的文件问index.html
在两个index.html下同时写入js代码document.domain="zeeliu.com"
这是域名都降为zeeliu.com
所以网页a.zeeliu.com(a.zeeliu.com:80/index.html)
和网页b.zeeliu.com(b.zeeliu.com:80/index.html)
之间就可以跨域数据交互了

下面我们通过代码在浏览器中测试

由于我们在同一个文件夹下测试所以两个index.html文件风别用a.html和b.html代替

代码如下(里面有解释)

a.html

<body>
    <style>
        .ct{
          width: 910px;
          margin: auto;
        }
        .main{
          float: left;
          width: 450px;
          height: 300px;
          border: 1px solid #ccc;
        }
        .main input{
          margin: 20px;
          width: 200px;
        }
        .iframe{
          float: right;
        }
        iframe{
          width: 450px;
          height: 300px;
          border: 1px dashed #ccc;
        }
      </style>
      
<!-- //面是样式从下面开始解读 -->
      <div class="ct">
        <h1>使用降域实现跨域</h1>
        <div class="main">
          <input type="text" placeholder="http://a.zeeliu.com:8080/a.html我是输入框默认显示">
        </div>
        
        <!-- //ifame标签可以在当前窗口建立一个子窗口;窗口显示b.zeeliu.com:8080/b.html这个域名的内容 -->
        <iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
      </div>

      <script>
      //URL: http://a.zeeliu.com:8080/a.html
      //添加一个事件输入框内容变化出发(input事件)
      document.querySelector('.main input').addEventListener('input', function(){
        console.log(this.value);//在控制台打印输入框输入的内容
        //window.frames获取当前窗口中的所有子窗口得到一个类数组对象,其实就是针对选中<ifame>创建的子窗口
        //下面的代码是选中窗口后在通过document.querySelector('input')选中b.html中的输入框(input)然后把当前窗口输入的值(this.value)赋给b.html中的input
        //就是a.html中输入什么b.html中就显示什么
        window.frames[0].document.querySelector('input').value = this.value;
      })
      
      //降域
      //只有a.html和b.html中同时降域为zeeliu.com上面的input事件中的赋值操作才生效
      //也就是a.html通过跨域操作了b.html中的内容
      document.domain = "zeeliu.com"
      </script>
</body>

b.html

<body>
        <style>
                html,body{
                    margin: 0;
                }
                input{
                    margin: 20px;
                    width: 200px;
                }
            </style>
            
                <input id="input" type="text"  placeholder="http://b.zeeliu.com:8080/b.html我是输入框默认显示">
            <script>
            // URL: http://b.zeeliu.com:8080/b.html
             
            document.querySelector('#input').addEventListener('input', function(){
                //同a.html中相同这边这边输入框输入上面同样赋值给那边
                //window.parent选择当前前窗口的父窗口一般只有一个
                window.parent.document.querySelector('input').value = this.value;
            })
            //降域
            document.domain = 'zeeliu.com';
            </script>
</body>

看上面的代码基本可以知道降域是怎么回事了;


下面是如何在本地建立服务器测试这个代码(包括修改hosts的内容)
首先把a.html和b.html放在同一个文件夹下
通过前面学到修改hosts的方法添加两个域名(如下图)

WX20170924-221140.png

然后在当前文件夹mock start创建模拟服务器

没有降域情况下(document.domain被注释)

如下图
输入的地址为a.zeeliu.com:8080/a.html
虚线框窗口引用的地址是b.zeeliu.com:8080/b.html
所以左边输入aaaa右边没有变化
【这是域不同 且没有降域】

WX20170924-221303.png

如下图
当输入的地址为b.zeeliu.com:8080/a.html及时没用降域也能实现左右同步
【这是因为他们的域相同】

WX20170924-221346.png

降域情况下

如下图
通过document.domain降域后及时域名不同也可以实现效果

WX20170924-221443.png

postMessage--------------------------------------

其实就是在不降域的情况a.html向b.html发送自己想给b.html的内容;然后b.html在接受这个内容(你情我愿);反正b向a也是一样

看代码

a.html代码

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>降域</title>
  <style>
    .ct{
      width: 910px;
      margin: auto;
    }
    .main{
      float: left;
      width: 450px;
      height: 300px;
      border: 1px solid #ccc;
    }
    .main input{
      margin: 20px;
      width: 200px;
    }
    .iframe{
      float: right;
    }
    iframe{
      width: 450px;
      height: 300px;
      border: 1px dashed #ccc;
    }
  </style>
</head>
<body>
  <div class="ct">
    <h1>使用postMessage实现跨域</h1>
    <div class="main">
      <input type="text" placeholder="http://a.zeeliu.com:8080/a.html">
    </div>
    <iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
  </div>
  <script>

  //当输入框内发生变化触发事件  
  $('.main input').addEventListener('input', function(){
    console.log(this.value);
    //将当前窗口的值通过.postMessage发送给window.frames[0]所选中的窗口
    //this.value是要发送的值;(可以使其他的值)
    //'*':代表任何网站;(当输入b.zeeliu.com:8080/b.html则只发给这个域名)
    window.frames[0].postMessage(this.value,'*');
  })

  //这个事件监听从b.html发送过来的数据;
  //e.data就是接收到的数据(b.html中的.postMessage()发送过来的)
  window.addEventListener('message',function(e) {
      $('.main input').value = e.data
      console.log(e.data);
  });
  function $(id){
    return document.querySelector(id);
  }
  </script>
</body>
</html>

b.html代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>降域</title>
    <style>
        html,body{
            margin: 0;
        }
        input{
            margin: 20px;
            width: 200px;
        }
    </style>
</head>
<body>
    <input id="input" type="text"  placeholder="http://b.zeeliu.com:8080/b.html">
    <script>
    $('#input').addEventListener('input', function(){
        //将当前窗口的值通过.postMessage发送给window.parent所选中的窗口
        //this.value是要发送的值;(可以使其他的值)
        //'*':代表任何网站;(当输入a.zeeliu.com:8080/a.html则只发给这个域名)
        window.parent.postMessage(this.value, '*');
    })

    //这个事件监听从a.html发送过来的数据;
    //e.data就是接收到的数据(a.html中的.postMessage()发送过来的)
    window.addEventListener('message',function(e) {
            $('#input').value = e.data
        console.log(e.data);
    });
    function $(id){
        return document.querySelector(id);
    }
    </script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 什么是同源策略 浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能...
    谢梦扬_阅读 436评论 0 0
  • 1.什么是同源策略浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不...
    24_Magic阅读 488评论 0 0
  • 1: 什么是同源策略 最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源",所谓...
    好奇而已阅读 290评论 0 0
  • 今天,我放下那么多的家务,就想为你写点东西,留下我们点点滴滴的回忆。未曾想,为你写的文字竟然得不到你的鼓...
    37度女王阅读 272评论 0 0
  • 我只是害怕有那么一天我不再愤世嫉俗,也活成了别人的附属品,而那一天到来之际我还没有强大到跟韩寒一样可以恣意...
    凌洛依阅读 962评论 0 0