持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
前言
本篇章主要讲述常见的跨域解决方案,跨域主要是因为浏览器同源策略引起的。
在之前的篇章中我们讲述了同源策略的产生背景,需要主要的是跨域是浏览器做出的限制,服务器与服务器通信并不存在跨域行为。
解决方案
CORS
在这篇 关于CORS的那点事 的文章里,我们介绍了什么是CORS以及如何设置等,我们需要知道的是CORS是目前处理跨域最流行的解决方案。
JSONP
JSONP的本质是通过script标签能够请求外部资源这一特性,让src属性链接外部资源实现跨域请求。这个跨域请求需要后端配合,让后端将数据以函数调用的形式返回,并告知浏览器以js文件执行。具体操作如下:
后端操作,以express服务器为例:
const data = [{a:1},{b:2},{c:3}]
(req,res,next) => {
const json = JSON.stringify(data);
const script = `callback(${json})`
res.header("content-type","application/javascript") //告知浏览器这是js文件,浏览器接收到会当js文件执行
res.send(script) //发送数据
}
假如前端不进行处理发送请求到后端的这个接口,浏览器控制台会报错,报错原因是callback这个函数不存在。所以,前端需要想要接收后端返回的数据,就需要定义一个函数接收这些数据。 前端操作:
function callback(data){
//在这个函数里就可以对data进行操作了
}
所以,从上面的例子中我们也可以看出JSONP的缺点也是显而易见:
- 影响服务器的响应格式。
- 只能使用get请求,因为script标签只会发送get请求。
Node中间件代理
我们都知道服务器与服务器是不存在跨域的,所以我们可以通过一个服务器充当媒介进行跨域处理:
大致模式如下:
flowchart TB
浏览器-->|请求|node服务器-->|请求| 目标服务器
目标服务器-->|响应|node服务器-->|响应| 浏览器
subgraph 浏览器
end
subgraph node服务器
end
subgraph 目标服务器
end
Nginx反向代理
实现思路和Node服务器代理一样,需要搭建一个nginx服务器用于转发请求。通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。 大致模式如下:
flowchart TB
浏览器-->|请求|服务器-->|请求| 目标服务器
目标服务器-->|响应|服务器-->|响应| 浏览器
subgraph 浏览器
end
subgraph 服务器
nginx服务器
end
subgraph 目标服务器
end
nginx配置大致如下:
// proxy服务器
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
}
}
postMessage
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一。
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
关于用法MDN上有详细的文档说明:window.postMessage
http://localhost:3000/a.html
<iframe src="http://localhost:6060/b.html" frameborder="0" id="frame" onload="load()"></iframe>//iframe加载完毕触发onload事件
<script>
function load() {
const frame = document.getElementById('frame')
frame.contentWindow.postMessage('hello', 'http://localhost:6060') //发送数据
window.onmessage = (e) => { //接受返回数据
console.log(e.data) //打印出hi
}
}
</script>
http://localhost:6060/b.html
window.onmessage = (e) => {
console.log(e.data) //打印出hello
e.source.postMessage('hi', e.origin) //响应对方数据
}
WebSocket
-
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。
-
WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。
-
WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
示例如下:
客户端代码:
const socket = new WebSocket('ws://localhost:6060'); //ws是WebSocket定义的协议
socket.onopen = () => socket.send('Hello');//向服务器发送数据
socket.onmessage = (e) =>console.log(e.data);//接收服务器返回的数据
服务器代码:
const express = require('express');
const app = express();
const WebSocket = require('ws');//ws 是一个第三方的 websocket 通信模块
const myWs = new WebSocket.Server({port:6060});
myWs.on('connection',(ws)=> {
ws.on('message', (data) => {
console.log(data); //打印客户端发送过来的数据
ws.send('我不爱你')
});
})
window.name + iframe
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)
flowchart TB
浏览器当前页面-window.name默认为空-->|设置window.name='aa'并在当前页打开新链接|浏览器新页面-新页面可以不同源-->|在新页面输出window.name| aa
subgraph 浏览器当前页面-window.name默认为空
end
subgraph 浏览器新页面-新页面可以不同源
end
subgraph aa
end
所以我们a和b两个页面想要跨域通信可以利用iframe+window.name,需要利用c页面作为媒介实现,c页面和a页面同源才能实现父子页面的iframe读取操作。
http://localhost:6060/a.html页面:
<iframe src="http://localhost:5050/b.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true //加锁
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
const iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:6060/c.html'; //设置完毕,iframe再次加载,此时加载的是c页面,a和c同源
first = false;
}else{
// 第2次onload后,此时是iframe是c页面,与a页面同源,a可以读取同域下iframe的window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
http://localhost:5050/b.html页面:
<script>
window.name = 'hello,I am b'
</script>
location.hash+iframe
实现原理: a.html 想和c.html 跨域通信,可以通过在a.html里嵌套iframe链接到 c.html,而c.html里又有一个iframe,链接到b.html,a和b是同域的。
三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。
flowchart TB
a.html#hello-->|嵌套|c.html
c.html-->|localtion.hash为hello-创建iframe链接到b.html#hi| b.html#hi
b.html#hi-->|将当前localtion.hash传给parent的parent的localtion.hash| a.html#hello
subgraph a.html#hello
end
subgraph b.html#hi
end
subgraph c.html
end
document.domain+iframe
实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。需要注意的是该方式只能用于二级域名相同的情况。
比如xueshu.baidu.com/a.html里嵌套一个iframe(链接到tieba.baidu.com/b.html),这种情况可以将两个页面设置同一个domain以实现跨域。
flowchart TB
xueshu.baidu.com/a.html-->|嵌套|tieba.baidu.com/b.html
tieba.baidu.com/b.html-->|设置document.domain=baidu.com| baidu.com
xueshu.baidu.com/a.html-->|设置document.domain=baidu.com| baidu.com
小结
- 跨域仅存在浏览器,脱离浏览器是不存在跨域的!
- CORS 支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案!