跨域通信的几种方式

同源策略里面的 指的是:协议、域名和端口。

只要这三者不同就会受到同源策略的影响,在浏览器控制台中,使用console.log(location.origin),输出的结果便是

IE是一个例外:端口号并未加入到同源策略的组成部分之中。所以http://example.com:8080/http://example.com属于同源并且不受任何限制。


常用方法

jsonp

  • 服务端 b.com/index.php:
<?php
    $arr=['domain'=>'come from: '.$_SERVER['HTTP_HOST']];
    $callback=$_GET['callback'];
    $json=$callback."(".json_encode($arr).");";
    echo $json;
  • 客户端 a.com/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
  <title>jsonp</title>
  <meta charset="UTF-8">
</head>
<body>
  <script>
    var url="http://b.com/index.php?callback=foo";
    //注意 foo函数的定义要先于 引入 url里的内容
    function foo(res){
      console.log(res);
    }

    function loadData(url){
      var elem=document.createElement('script');
      elem.src=url;
      document.getElementsByTagName('head')[0].appendChild(elem);
    }
    loadData(url);
  </script>
</body>
</html>

加载 此页面a.com/index.html,对 b.com/index.php 发起请求,后者将会对前者返回:

foo({domain: "come from: 182.254.152.115:8080"});

于是调用 a.com/index.html 中的 foo() 函数,a.com/index.html 页面所在的控制台输出:

{ domain: "come from: 182.254.152.115:8080" }

jsonp的缺点:
1. 无法在 post请求类型中使用
2. 只能做到动态创建 script标签并读取数据而无法从客户端发出数据


CORS (XMLHttpRequest level 2)

XMLHttpRequest无法跨源通信,但在 XMLHttpRequest level2中 只要服务器端许可就可以实现

  • 服务端 b.com/index.php
<?php
    header('Access-Control-Allow-Origin:http://a.com');
    header('Access-Control-Allow-Credentials:true');
    header('Access-Control-Allow-Methods:POST');
    echo file_get_contents("php://input");
  • 客户端 a.com/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <title>jsonp</title>
  <meta charset="UTF-8">
</head>
<body>
<script>
  function foo(res) {
    console.log(res);
  }
  function xhr(url, params, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.withCredentials = true;
    var headerType = typeof params==='string' ? 'application/x-www-form-urlencoded' : 'multipart/form-data';
    xhr.setRequestHeader('Content-Type', headerType);

    if (callback) {
      xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) {
            return;
        }
        callback(xhr.responseText || '');
      };
    }
    xhr.send(params);
  }
  xhr('http://b.com/index.php', 'id=9999', foo);
</script>
</body>
</html>

加载 此页面a.com/index.html,对 b.com/index.php 发起请求,后者将会对前者返回:

id=9999

于是调用 a.com/index.html 中的 foo() 函数,a.com/index.html 页面所在的控制台输出:

id=9999

postMessage()

语法:

originWindow.postMessage(message, targetOrigin);

originWindow:要发起请求的窗口引用
message: 要发送出去的消息,可以是字符串或者对象
targetOrigin: 指定哪些窗口能够收到消息,其值可以是字符串*(表示无限制)或者一个确切的URI

只有目标窗口的协议、主机地址或端口这三者全部匹配才会发送消息。

  • 父窗口 b.com/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Parent window</title>
</head>
<body>
<div>
    <iframe id="child" src="http://a.com/index.html"></iframe>
</div>
<input id="p_input" type="txet" value="0">
<button onclick='p_add()'>parent click to add</button>

<script type="text/javascript">
    window.addEventListener('message',function(e){
        document.getElementById('p_input').value = e.data;
    });

    function p_add(){
      var p_input = document.getElementById('p_input');
      var value = +p_input.value+1;
      p_input.value= value;
      window.frames[0].postMessage(value,'http://a.com');
    }
</script>
</body>
</html>
  • 子窗口 a.com/index.html
<!doctype html>
<html>
<head>
    <title>Child window</title>
</head>
<body>
  <input id="c_input" type="text" value="0">
  <button onclick='c_add()'>child click to add</button>
<script type="text/javascript">
    var c_input=document.getElementById('c_input');

    window.addEventListener('message', function(e) {
        if(e.source!=window.parent) return;
        var value = +c_input.value+1;
        c_input.value = value;
    });

    function c_add(){
        var value = +c_input.value+1;
        c_input.value = value;
        window.parent.postMessage(value,'http://b.com');
    }
</script>
</body>
</html>

beacon (ping)

ping协议其实是一种很古老的跨域方法。它的本质是利用了浏览器可以获取不同域名下图片的原理,把请求参数放在了图片地址的URL参数中发给后端。

  • 客户端 a.com/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Post Message</title>
</head>
<body>
<script type="text/javascript">
  function foo() {
    console.log('ok');
  }
  function ping(url, callback) {
    var img = new Image();
    var done = function () {
      // 图片实例不需要插入到页面DOM树中,避免了返回图片展示出来被用户看到
      img.onload = img.onerror = img.onabort = null;
      img = null;
      if (callback) {
        callback();
      }
    };
    img.onload = img.onerror = img.onabort = done;
    img.src = url;
  }
  ping('http://b.com/index.php?id=9078', foo);

  // 通过200ms的JS死循环来延迟浏览器关闭,为发送ping请求争取到更多的时间,确保请求发送成功
  var end;
  var delay = 200;
  var now = +new Date();
  for (end = now + delay; now < end;) {
      now = +new Date();
  }
</script>
</body>
</html>
  • 服务端 b.com/index.php
<?php
    header("HTTP/1.1 204 No Content");

submit协议

适合大数据量或者文件的请求发送,需要浏览器支持FormData

  • 客户端 a.com/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Post Message</title>
</head>
<body>
  <form id="form">
    <input type="text" name="text1" value="1">
    <input type="text" name="text2" value="2">
    <input type="submit">
  </form>

<script type="text/javascript">
  function foo() {
    console.log('ok');
  }

  function xhr(url, params, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.withCredentials = true;
    var headerType = typeof params==='string' ? 'application/x-www-form-urlencoded' : 'multipart/form-data';
    xhr.setRequestHeader('Content-Type', headerType);

    if (callback) {
      xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) {
            return;
        }
        callback(xhr.responseText || '');
      };
    }
    xhr.send(params);
  }

  var formElement = document.getElementById('form');
  formElement.onsubmit = function (e) {
      // 提交时改为XHR提交
      e.preventDefault();
      var formData = new FormData(formElement);
      xhr('http://b.com/index.php', formData, foo);
  };
</script>
</body>
</html>
  • 客户端 b.com/index.php
<?php
    header('Access-Control-Allow-Origin:http://a.com');
    header('Access-Control-Allow-Credentials:true');
    header('Access-Control-Allow-Methods:POST');

如何选择

_jsonpCORSpostMessagebeaconsubmit
提交的数据量少(2083字符)少(2048字符)
支持文件上传不支持不支持不支持不支持支持
页面关闭时发送不支持不支持不支持可以支持(手动设置死循环,锁定浏览器)不支持
浏览器支持程度支持度良好支持度良好部分支持(必须支持 postMessage的浏览器)支持度良好部分支持(必须支持 FormData的浏览器)

从表中可以看出来:
JSONP协议主要适合于拉取数据、获取配置等不需要大数据量提交的操作;
beacon协议则更适用于不要关心返回结果的少数据量提交,尤其适合在页面关闭是发送数据;
submit协议则适用于大数据量的提交,尤其适合文件上传;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值