前端跨域请求、跨页面通信方式

Posted by Mars at

前端跨域请求、跨页面通信的几种方式梳理

Refer: 阮一峰CORS

一、跨域问题

1. 什么是同源策略?什么时候出现跨域问题?

在HTML标签(比如img,script等)和CSS中(url函数)使用url进行资源请求,浏览器默认不设任何限制,可以对任何资源url进行请求。

一般情况下,下列三种情况只能请求同源的资源,非同源的请求默认会报错(跨域错误):

  • 读取Cookie、LocalStorage 和 IndexDB
  • DOM 无法获得
  • Js发送AJAX请求

这是浏览器的一种基本的保护策略,叫做同源策略。

同源策略

协议、域名和端口号都相同的请求,叫做同源请求。JS代码内只能发出同源请求。

即便两个不同的域名指向同一个 ip 地址,也非同源。

2. 跨域AJAX请求的几种常见解决方式

2.1 跨域资源共享(CORS:Cross Origin Resource Sharing)

CORS是W3C标准,是跨域请求的根本解决方式。

实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

浏览器将请求分为两种:简单请求非简单请求

简单请求

(1) 请求方法是以下三种方法之一:

HEAD

GET

POST

(2)HTTP的头信息不超出以下几种字段: Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求和非简单请求,浏览器的发送流程如下图所示:

CORS的简单请求和非简单请求

2.2 JSONP

JSONP是一种古老的广泛使用的跨域请求方式,好处是可以兼容很多老式的浏览器。

JSONP主要是利用了script标签内src属性请求不受跨域限制的特点。

JSONP的主要流程:

  1. 在页面引入或写好要执行的函数;
  2. 创建一个<script>标签或动态创建一个script元素,src属性为跨域请求的地址,地址的最后用?callback=funcName,向服务器标记函数方法名;
  3. 挂载这个script元素,发出请求;
  4. 服务器收到这个请求后,解析callback后的函数名funcName,然后返回一个funcName函数执行的代码字符串,里面包含了服务器想要浏览器执行的操作;
  5. 浏览器接收响应,作为代码执行。
<!--这里定义要执行的函数-->
<script>
   function show(data){
      alert(data)
   }
</script>

<!-- 在另一个script元素内发出跨域请求,后端收到show这个请求,然后返回带数据的show函数字符串-->
<!-- 因为是script标签,返回的数据作为代码直接在浏览器执行。-->
<script src="http://server?callback=show>

JSONP的局限性:JSONP只能发GET请求。

2.3 WebSocket

WebSocket是一种协议,它不受同源策略限制。

只要服务器支持,使用WebSocket协议(ws:// 或 wss://)进行请求即可。

2.4 代理服务器

服务器间通信不受同源策略限制。因此,可以在本地开启一个同源服务器,用同源服务器与目标服务器进行跨域请求通信,然后页面在本地与本地服务器进行同源请求即可。

二、 跨页面传递数据(跨窗口通信)

同源策略规定:

如果我们有对另外一个窗口(例如,一个使用 window.open 创建的弹窗,或者一个窗口中的 iframe)的引用,并且该窗口是同源的,那么我们就具有对该窗口的全部访问权限。

否则,如果该窗口不是同源的,那么我们就无法访问该窗口中的内容:变量,文档,任何东西。唯一的例外是 location:我们可以修改它(进而重定向用户)。但是我们无法读取 location(因此,我们无法看到用户当前所处的位置,也就不会泄漏任何信息)。

非同源的窗口,还可以通过postMessage向其发送一条消息。这是对于非同源页面引用唯二的两个操作。

— 现代JS教程

要想让同一个二级域下的所有子域都被视作同源,需要在每个页面上添加以下代码:

// 为当前页面设置域(默认为当前页面URL的域名)
document.domain = 'site.com';

1. 同源跨窗口通信

1.1 Broadcast Channel API (广播频道)

Broadcast Channel API 可以实现同源下浏览器不同窗口,Tab页,frame或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面)之间的简单通讯。

// 1.连接到test_channel广播频道,如果还没有这个频道,这代表创建一个名叫test_channel的广播频道
var bc = new BroadcastChannel('test_channel');

// 2.向广播信道发送消息
bc.postMessage('This is a test message.');

// 3.其他所有链接到这个广播频道的页面,可以接收到一个message事件
bc.onmessage = (e)=>{
    console.log(e)
}

// 4.断开频道连接
bc.close()

1.2 利用localStorage

同源的页面,可以访问同一个localStorage。

通过对localStorage的修改进行监听(storage事件),可以实现跨页面通信。

2. 非同源跨窗口通信

2.1 可以获取到目标窗口的引用时: postMessage

这种情况包括嵌入iframe,使用window.open打开这类情况。

跨窗口通信有以下三步:

  1. 获取到目标窗口的引用win,然后调用win.postMessage(message, targetOrigin)方法

这里: data是要发送的数据,targetOrigin是目标窗口的源。

也就是说,只有目标窗口在指定的源下(协议、域和端口),才能正常接收到消息。

这保护了目标页面的安全,因为发送方此时与目标页面不同源,因此不能知道目标页面现在实际的源,用户可以随时更改到任何源。这样设定保证了数据不会被发送到非目标源的页面。

  1. 在目标页面上,写入好window.onmessage事件,用于监听收取来自外部页面的message。
window.onmessage = (e) => {
// do someting with the message.
// 此时的e具有三个内部属性: ①data:数据本身;②origin:发送方的源; ③source:发送方窗口的引用(可以随时使用e.source.postMessage反向发送消息。); 
}
Keywords: 跨域
previousPost nextPost
已经有 1000000 个小伙伴看完了这篇推文。