【浏览器系列】【万字详解】实现前后端跨域通信的几种方式

目录

前言:

1. 同源策略的概念和具体限制

1.1 同源策略是什么?

1.2 为什么要用同源策略?

1.3 同源策略的限制范围?

1.4 总结

2. 前后端如何通信

知识拓展:为什么Ajax跨域访问被禁止?

3. 如何创建AJax

发送 Ajax 请求的五个步骤(XMLHttpRequest的工作原理)

发送get、post请求

onreadystatechange 事件

事件的触发条件

事件的触发顺序

实际开发中用的 原生Ajax请求

4. 跨域通信的几种方式

1、JSONP

2、WebSocket

3、CORS

4、Hash

5、postMessage()方法

前言:

        前端通信类的问题,主要包括以下内容:

  • 什么是同源策略及限制
  • 前后端如何通信
  • 如何创建Ajax
  • 跨域通信的几种方式(这部分非常重要)

1. 同源策略的概念和具体限制

         跨域问题就是出于浏览器的【同源策略】限制。而同源策略是一个概念,就一句话。有什么限制,就三句话。能说出来即可。

1.1 同源策略是什么?

同源策略:限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。(来自MDN官方的解释)

上面官方解释可能不太理解,太抽象。下面通俗解释:

所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
由于我们现在是采用的前后端分离的微服务架构,前端和后端必定存在跨域问题。

​ 跨域问题正是出于浏览器的【同源策略】限制。同源策略(Sameoriginpolicy)是一种约定,
它是浏览器最核心也最基本的安全功能,
如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略会阻止一个域的JavaScript脚本和另外一个域的内容进行交互。

满足同源的三个条件:
所谓同源是指,域名、协议、端口相同。

具体解释:

(1)包括三个部分:协议、域名、端口(http协议的默认端口是80)。如果有任何一个部分不同,则不同,那就是跨域了。

(2)限制这个源的文档没有权利去操作另一个源的文档。这个限制体现在:(要记住)三句话,在1.3限制范围里

1.2 为什么要用同源策略?

同源策略存在的意义
非同源下的 cookie 等隐私数据可以被随意获取
非同源下的 DOM 可以的随意操作
ajax 可以任意请求的话,用户的各种隐私肯定会泄露,对用户造成不同程度的损失

1.3 同源策略的限制范围?

不能获取不同源的 cookie,LocalStorage 和 indexDB
不能获取不同源的 DOM()
不能发送不同源的 ajax 请求 (可以向不同源的服务器发起请求,但是返回的数据会被浏览器拦截)

1.4 总结

        概念:限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

        限制:

  • Cookie、LocalStorage和IndexDB无法获取。

  • 无法获取和操作DOM。

  • 不能发送Ajax请求。我们要注意,Ajax只适合同源的通信。

2. 前后端如何通信

主要有以下几种方式:

  • Ajax:不支持跨域。

  • WebSocket:不受同源策略的限制,支持跨域。

  • CORS:不受同源策略的限制,支持跨域。一种新的通信协议标准。可以理解成是:同时支持同源和跨域的Ajax

知识拓展:为什么Ajax跨域访问被禁止?

要讲清楚这个问题,首先要谈谈Cookie

1.客户向A网站的服务器发送登录请求,并携带账号密码数据

2.A网站的服务器校验账号密码正确后,返回响应并给本地添加了Cookie

3.之后客户再次向A网站发起请求会自动带上A网站存储在本地的cookie

4.A网站的服务器从cookie中获取账号密码数据后,返回登陆成功界面。

然后举个例子就理解为什么了

  假设有一个黑客叫做小黑,他从网上抓取了一堆美女图做了一个网站,每日访问量爆表。

  为了维护网站运行,小黑挂了一张收款码,觉得网站不错的可以适当资助一点,可是无奈伸手党太多,小黑的网站入不敷出。

  于是他非常生气的在网页中写了一段js代码,使用ajax向淘宝发起登陆请求,因为很多数人都访问过淘宝,所以电脑中存有淘宝的cookie,不需要输入账号密码直接就自动登录了,然后小黑在ajax回调函数中解析了淘宝返回的数据,得到了很多人的隐私信息,转手一卖,小黑的网站终于盈利了。

  如果跨域也可以发送AJAX请求的话,小黑就真的获取到了用户的隐私并成功获利了!!!
  为了防止小黑这种黑客侵犯用户的隐私,同源政策出现了。

  同源政策:不是同协议 同域名 同端口 的网页无法相互访问。

  用form表单提交到不同源的网页是被允许的,因为 form 提交到另一个域名之后,原页面的脚本无法获取新页面中的内容,所以浏览器认为这是安全的。

  而 AJAX 是可以读取响应内容的,因此浏览器不能允许你这样做。如果你细心的话你会发现,其实请求已经发送出去了,你只是拿不到响应而已。

  所以浏览器这个策略的本质是,一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。

3. 如何创建AJax

在回答 Ajax 的问题时,要回答以下几个方面:

  • 1、XMLHttpRequest 的工作原理

  • 2、兼容性处理

XMLHttpRequest只有在高级浏览器中才支持。在回答问题时,这个兼容性问题不要忽略。

  • 3、事件的出发条件

  • 4、事件的触发顺序

XMLHttpRequest有很多触发事件,每个事件是怎么触发的。

发送 Ajax 请求的五个步骤(XMLHttpRequest的工作原理)

(1)创建XMLHttpRequest 对象。

(2)使用open方法设置请求的参数。open(method, url, 是否异步)。

(3)发送请求。

(4)注册事件。 注册onreadystatechange事件,状态改变时就会调用。

如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。

(5)获取返回的数据,更新UI。

Ajax 的推荐链接:javascript - 聊聊Ajax那些事_个人文章 - SegmentFault 思否

发送get、post请求

get请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h1>Ajax 发送 get 请求</h1>
<input type="button" value="发送get_ajax请求" id='btnAjax'>

<script type="text/javascript">
    // 绑定点击事件
    document.querySelector('#btnAjax').onclick = function () {
        // 发送ajax 请求 需要 五步

        // (1)创建异步对象
        var ajaxObj = new XMLHttpRequest();

        // (2)设置请求的参数。包括:请求的方法、请求的url。
        ajaxObj.open('get', '02-ajax.php');

        // (3)发送请求
        ajaxObj.send();

        //(4)注册事件。 onreadystatechange事件,状态改变时就会调用。
        //如果要在数据完整请求回来的时候才调用,我们需要手动写一些判断的逻辑。
        ajaxObj.onreadystatechange = function () {
            // 为了保证 数据 完整返回,我们一般会判断 两个值
            if (ajaxObj.readyState == 4 && ajaxObj.status == 200) {
                // 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
                // 5.在注册的事件中 获取 返回的 内容 并修改页面的显示
                console.log('数据返回成功');

                // 数据是保存在 异步对象的 属性中
                console.log(ajaxObj.responseText);

                // 修改页面的显示
                document.querySelector('h1').innerHTML = ajaxObj.responseText;
            }
        }
    }
</script>
</body>
</html>

post 请求举例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h1>Ajax 发送 get 请求</h1>
<input type="button" value="发送put_ajax请求" id='btnAjax'>
<script type="text/javascript">

    // 异步对象
    var xhr = new XMLHttpRequest();

    // 设置属性
    xhr.open('post', '02.post.php');

    // 如果想要使用post提交数据,必须添加此行
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

    // 将数据通过send方法传递
    xhr.send('name=fox&age=18');

    // 发送并接受返回值
    xhr.onreadystatechange = function () {
        // 这步为判断服务器是否正确响应
        if (xhr.readyState == 4 && xhr.status == 200) {
            alert(xhr.responseText);
        }
    };
</script>
</body>
</html>

onreadystatechange 事件

注册 onreadystatechange 事件后,每当 readyState 属性改变时,就会调用 onreadystatechange 函数。

readyState:(存有 XMLHttpRequest 的状态。从 0 到 4 发生变化)

  • 0: 请求未初始化

  • 1: 服务器连接已建立

  • 2: 请求已接收

  • 3: 请求处理中

  • 4: 请求已完成,且响应已就绪

事件的触发条件

事件的触发顺序

上图的参考链接:

实际开发中用的 原生Ajax请求


    var util = {};

    //获取 ajax 请求之后的json
    util.json = function (options) {

        var opt = {
            url: '',
            type: 'get',
            data: {},
            success: function () {
            },
            error: function () {
            },

        };
        util.extend(opt, options);
        if (opt.url) {
            //IE兼容性处理:浏览器特征检查。检查该浏览器是否存在XMLHttpRequest这个api,没有的话,就用IE的api
            var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');

            var data = opt.data,
                url = opt.url,
                type = opt.type.toUpperCase();
            dataArr = [];
        }

        for (var key in data) {
            dataArr.push(key + '=' + data[key]);
        }

        if (type === 'GET') {
            url = url + '?' + dataArr.join('&');
            xhr.open(type, url.replace(/\?$/g, ''), true);
            xhr.send();
        }

        if (type === 'POST') {
            xhr.open(type, url, true);
            // 如果想要使用post提交数据,必须添加此行
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhr.send(dataArr.join('&'));
        }

        xhr.onload = function () {
            if (xhr.status === 200 || xhr.status === 304) { //304表示:用缓存即可。206表示获取媒体资源的前面一部分
                var res;
                if (opt.success && opt.success instanceof Function) {
                    res = xhr.responseText;
                    if (typeof res === 'string') {
                        res = JSON.parse(res);  //将字符串转成json
                        opt.success.call(xhr, res);
                    }
                }
            } else {
                if (opt.error && opt.error instanceof Function) {
                    opt.error.call(xhr, res);
                }
            }
        };
    }


 Ajax 的推荐链接:javascript - 聊聊Ajax那些事_个人文章 - SegmentFault 思否

4. 跨域通信的几种方式

方式如下:

  • 1、JSONP

  • 2、WebSocket

  • 3、CORS

  • 4、Hash

  • 5、postMessage

上面这五种方式,在面试时,都要说出来。

1、JSONP

面试会问:JSONP的原理是什么?怎么实现的?

在CORS和postMessage以前,我们一直都是通过JSONP来做跨域通信的。

JSONP的原理:通过<script>标签的异步加载来实现的。比如说,实际开发中,我们发现,head标签里,可以通过<script>标签的src,里面放url,加载很多在线的插件。这就是用到了JSONP。

JSONP的实现:

比如说,客户端这样写:

    <script src="http://www.smyhvae.com/?data=name&callback=myjsonp"></script>

上面的src中,data=name是get请求的参数,myjsonp是和后台约定好的函数名。
服务器端这样写:

        myjsonp({
            data: {}

        })

于是,本地要求创建一个myjsonp 的全局函数,才能将返回的数据执行出来。

实际开发中,前端的JSONP是这样实现的:

<script>

    var util = {};

    //定义方法:动态创建 script 标签
    /**
     * [function 在页面中注入js脚本]
     * @param  {[type]} url     [description]
     * @param  {[type]} charset [description]
     * @return {[type]}         [description]
     */
    util.createScript = function (url, charset) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        charset && script.setAttribute('charset', charset);
        script.setAttribute('src', url);
        script.async = true;
        return script;
    };


    /**
     * [function 处理jsonp]
     * @param  {[type]} url      [description]
     * @param  {[type]} onsucess [description]
     * @param  {[type]} onerror  [description]
     * @param  {[type]} charset  [description]
     * @return {[type]}          [description]
     */
    util.jsonp = function (url, onsuccess, onerror, charset) {
        var callbackName = util.getName('tt_player'); //事先约定好的 函数名
        window[callbackName] = function () {      //根据回调名称注册一个全局的函数
            if (onsuccess && util.isFunction(onsuccess)) {
                onsuccess(arguments[0]);
            }
        };
        var script = util.createScript(url + '&callback=' + callbackName, charset);   //动态创建一个script标签
        script.onload = script.onreadystatechange = function () {   //监听加载成功的事件,获取数据
            if (!script.readyState || /loaded|complete/.test(script.readyState)) {
                script.onload = script.onreadystatechange = null;
                // 移除该script的 DOM 对象
                if (script.parentNode) {
                    script.parentNode.removeChild(script);
                }
                // 删除函数或变量
                window[callbackName] = null;  //最后不要忘了删除
            }
        };
        script.onerror = function () {
            if (onerror && util.isFunction(onerror)) {
                onerror();
            }
        };
        document.getElementsByTagName('head')[0].appendChild(script); //往html中增加这个标签,目的是把请求发送出去
    };

</script>

2、WebSocket

WebSocket的用法如下:

    //

    var ws = new WebSocket('wss://echo.websocket.org'); //创建WebSocket的对象。参数可以是 ws 或 wss,后者表示加密。

    //把请求发出去
    ws.onopen = function (evt) {
        console.log('Connection open ...');
        ws.send('Hello WebSockets!');
    };


    //对方发消息过来时,我接收
    ws.onmessage = function (evt) {
        console.log('Received Message: ', evt.data);
        ws.close();
    };

    //关闭连接
    ws.onclose = function (evt) {
        console.log('Connection closed.');
    };

还有一个对于WebSocket很好入门的一篇博客:(30条消息) 新手入门:websocket_一只BOY猿的博客-CSDN博客_websocket

Websocket的推荐链接:WebSocket 教程 - 阮一峰的网络日志

3、CORS

CORS 可以理解成是既可以同步、也可以异步*的Ajax。

fetch 是一个比较新的API,用来实现CORS通信。用法如下:

      // url(必选),options(可选)
      fetch('/some/url/', {
          method: 'get',
      }).then(function (response) {  //类似于 ES6中的promise

      }).catch(function (err) {
        // 出错了,等价于 then 的第二个参数,但这样更好用更直观
      });

4、Hash

url的#后面的内容就叫Hash。Hash的改变,页面不会刷新。这就是用 Hash 做跨域通信的基本原理。

补充:url的?后面的内容叫Search。Search的改变,会导致页面刷新,因此不能做跨域通信。

使用举例:

场景:我的页面 A 通过iframe或frame嵌入了跨域的页面 B。

现在,我这个A页面想给B页面发消息,怎么操作呢?

(1)首先,在我的A页面中:

    //伪代码
    var B = document.getElementsByTagName('iframe');
    B.src = B.src + '#' + 'jsonString';  //我们可以把JS 对象,通过 JSON.stringify()方法转成 json字符串,发给 B

(2)然后,在B页面中:

    // B中的伪代码
    window.onhashchange = function () {  //通过onhashchange方法监听,url中的 hash 是否发生变化
        var data = window.location.hash;
    };

5、postMessage()方法

H5中新增的postMessage()方法,可以用来做跨域通信。既然是H5中新增的,那就一定要提到。

场景:窗口 A (http:A.com)向跨域的窗口 B (http:B.com)发送信息。步骤如下。

(1)在A窗口中操作如下:向B窗口发送数据:

	// 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息
 	Bwindow.postMessage('data', 'http://B.com'); //这里强调的是B窗口里的window对象

(2)在B窗口中操作如下:

    // 在窗口B中监听 message 事件
    Awindow.addEventListener('message', function (event) {   //这里强调的是A窗口里的window对象
        console.log(event.origin);  //获取 :url。这里指:http://A.com
        console.log(event.source);  //获取:A window对象
        console.log(event.data);    //获取传过来的数据
    }, false);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码上游

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值