跨域通信

同源策略和跨域

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

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSSCSRF等攻击。所谓同源是指”协议+域名+端口“三者相同,即便两个不同的域名指向同一个ip地址,也非同源

同源策略限制内容有:

  • CookieLocalStorageIndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求

但是有的标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

特别说明两点:

第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。

第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

Ajax

Asynchronous Javascript And XML是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助。简单地说,在不需要重新刷新页面的情况下,Ajax 通过异步请求加载后台数据,并在网页上呈现出来。

原生Ajax

发送 Ajax 请求的五个步骤

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。

  • 创建XMLHttpRequest 对象。const xhr = new XMLHttpRequest()

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

    • get可以把请求参数拼接在url

    • 如果想要使用post提交数据,必须添加此行。

      xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

  • 发送请求。

    • get请求:xhr.send()
    • post请求:将数据通过send方法传递 xhr.send('name=fox&age=18');
  • 注册事件。 注册onreadystatechange事件,状态改变时就会调用。

    • xhr.readyState===4
    • (xhr.status>=200 && xhr.status<300) || xhr.status === 304 304:服务器端资源未改变,可直接使用客户端未过期的缓存。
  • 获取返回的数据,更新UI。

onreadystatechange 事件

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

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

  • 0: 请求未初始化

  • 1: 服务器连接已建立

  • 2: 请求已接收

  • 3: 请求处理中

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

兼容性

IE浏览器通过 XMLHttpRequest 或者 ActiveXObject (IE6及更低版本浏览器)对象同样实现了ajax的功能。

Promise封装axios

axios是一个基于promiseHTTP库,可以用在浏览器或者node.js中。

axios是通过promise实现对ajax技术的一种封装。

axios提供两个http请求适配器,XHR和HTTP。XHR的核心是浏览器端的XMLHttpRequest对象;HTTP的核心是node的http.request方法。

特性

  • 从浏览器中创建XMLHttpRequests
  • 从node.js创建http请求
  • 支持promise API
  • 拦截请求与响应
  • 转换请求数据与响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 发送异步ajax请求的函数模块
* 封装axios库
* 函数返回值promise对象
* 优化1: 统一处理请求异常?
* 在外层包一个自己创建的promise对象
* 在请求出错时, 不reject(error), 而是显示错误提示
* 优化2: 异步得到不是reponse, 而是response.data
* 在请求成功resolve时: resolve(response.data)
*/
import axios from 'axios'
import { message } from 'antd'
export default function ajax(url,data={},type='GET') {
return new Promise((resolve,reject) => {
let promise;
// 执行异步ajax请求
if(type==='GET'){//发GET请求
promise=axios.get(url,{
params:data
})
}else{//发POST请求
promise=axios.post(url,data)
}
// 如果请求成功了,调用resolve(value)
// 该方法返回一个以response.data值解析后的Promise对象
promise.then(response => {
resolve(response.data)
// console.log(response.data)
// 如果请求失败了,不调用reject(reason),而是提示异常信息
}).catch(error => {
message.error('请求出错了:'+error.message)
})
})

}

跨域解决方案

JSONP

JSONP原理

JSON with Padding JS函数包裹JSON数据。

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

<script> 标签中type的默认属性是text/javascript,请求内容就会被浏览器以JS代码进行执行。

JSONPAJAX相同,都可以从客户端向服务器端发送请求获取数据。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)。

JSONP优缺点

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。

缺点是仅支持get方法,具有局限性;不安全,可能会遭受XSS攻击。

JSONP的实现流程

  • 声明一个回调函数,其函数名(如jsonpCallback)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
  • 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给<script>src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=jsonpCallback)。
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串。
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(jsonpCallback),对返回的数据进行操作。
1
2
3
4
5
6
7
8
9
10
11
//去创建一个script标签
const script = document.createElement("script");
//script的src属性设置接口地址 并带一个callback回调函数名称
script.src = "http://127.0.0.1:8888/index.php?callback=jsonpCallback";
//插入到页面
document.head.appendChild(script);
//通过定义函数名去接收后台返回数据
function jsonpCallback(data){
//注意 jsonp返回的数据是json对象可以直接使用
//ajax 取得数据是json字符串需要转换成json对象才可以使用。
}

在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要一个封装好的JSONP函数。

React调用jsonp的库

yarn add jsonp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import jsonp from 'jsonp'
import {message} from 'antd'
export const reqWeather = (city) => {

return new Promise((resolve, reject) => {
const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city}&output=json&ak=3p49MVra6urFRGOT9s8UBWr2`
// 发送jsonp请求 这里的callback是箭头函数
jsonp(url, {}, (err, data) => {
console.log('jsonp()', err, data)
// 如果成功了
if (!err && data.status==='success') {
// 取出需要的数据
const {dayPictureUrl, weather} = data.results[0].weather_data[0]
resolve({dayPictureUrl, weather})
} else {
// 如果失败了
message.error('获取天气信息失败!')
}

})
})
}
// reqWeather('北京')

JSONP请求的简单例子

客户端 http://127.0.0.1:5500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
用户名:<input type="text" id="username"> <button>提交</button>
<p></p>
<script>
//获取 input和p元素
const input = document.getElementById("username");
const btn = document.querySelector('button');
const p = document.querySelector('p');
//声明回调函数
function handle(data) {
//修改 p 标签的提示文本
p.innerHTML = data.msg;
console.log(data)
}
//绑定事件
btn.addEventListener('click', function () {
//获取用户的输入值
let username = input.value;
console.log(username)
//向服务器端发送请求 检测用户名是否存在
//1. 创建 script 标签
const script = document.createElement('script');
//2. 设置标签的 src 属性
script.src = `http://127.0.0.1:8000/jsonp-server?name=${username}&callback=handle`;
//3. 将 script 插入到文档中
document.body.appendChild(script);
});
</script>
</body>

服务器 http://127.0.0.1:8000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//1. 引入express
const express = require('express');

//2. 创建应用对象
const app = express();

//3. 创建路由规则
// request 是对请求报文的封装
// response 是对响应报文的封装

//jsonp服务
const username=[];
app.all('/jsonp-server',(request, response) => {
const {name,callback} = request.query;
const data = {msg:"用户名设置成功"};
username.includes(name) ? data.msg="用户名已存在" : username.push(name);
console.log(username)
//将数据转化为字符串
let str = JSON.stringify(data);
//返回结果
response.end(`${callback}(${str})`);
});

//4. 监听端口启动服务
app.listen(8000, ()=>{
console.log("服务已经启动, 8000 端口监听中....");
});

CORS

CORS(Cross-Origin Resource Sharing),跨域资源共享。

CORS是官方的跨域解决方案,需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现

通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。

浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符*则表示所有网站都可以访问资源。

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

只要同时满足以下两大条件,就属于简单请求

请求方法是 HEADGETPOST 三种之一;

HTTP 头信息不超过右边着几个字段:AcceptAccept-LanguageContent-LanguageLast-Event-ID Content-Type 只限于三个值 application/x-www-form-urlencodedmultipart/form-datatext/plain

复杂请求

不符合以上条件的请求,比如请求方法是 PUTDELETE,或 Content-Type 值为 application/json。浏览器会在正式通信之前,发送一次 HTTP 预检 OPTIONS 请求,先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 请求方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XHR 请求,否则报错。

CORS完整复杂请求的例子

客户端 http://127.0.0.1:5500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>
<button>发送请求</button>
<script>
const btn = document.querySelector('button');
btn.addEventListener('click',function (){
const xhr=new XMLHttpRequest();
document.cookie="name=leslie";// cookie不能跨域
xhr.withCredentials=true;// 前端设置是否带cookie
xhr.open('PUT','http://127.0.0.1:8000/cors', true);
xhr.setRequestHeader('name','leslie');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
if((xhr.status>=200 && xhr.status<300) || xhr.status === 304){
console.log(xhr.response);
//得到响应头,后台需设置Access-Control-Expose-Headers
console.log(xhr.getResponseHeader('name'))
}
}
}


})

</script>
</body>

服务器 http://127.0.0.1:8000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//1. 引入express
const express = require('express');
const WebSocket = require('ws');//记得安装ws
//2. 创建应用对象
const app = express();
// 把server.js文件所在的目录设置为静态文件目录
app.use(express.static(__dirname));
//3. 创建路由规则
// request 是对请求报文的封装
// response 是对响应报文的封装
//CORS
let whitList = ['http://127.0.0.1:5500'] //设置白名单
app.use(function (req, res, next) {
let origin = req.headers.origin
if (whitList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader('Access-Control-Allow-Origin', origin)
// 允许携带哪个头访问我
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if (req.method === 'OPTIONS') {
res.end() // OPTIONS请求不做任何处理
}
}
next()
})
app.put('/cors', function (req, res) {
console.log(req.headers)
res.setHeader('name', 'lw') //返回一个响应头,后台需设置
res.end('我不爱你')
})

//4. 监听端口启动服务
app.listen(8000, () => {
console.log("服务已经启动, 8000 端口监听中....");
});

第三方中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const express = require('express');
const cors = require('cors');
//2. 创建应用对象
const app = express();
//CORS
app.use(
cors({
"origin": "http://127.0.0.1:5500",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"allowedHeaders": "name",
"exposedHeaders": "name",
"credentials": true,
"maxAge": 6,
// "preflightContinue": true,
})
);

想要传递 cookie 需要满足 3 个条件

  • web 请求设置withCredentials

这里默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.

1
2
3
4
5
// 原生 xml 的设置方式
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;

2.Access-Control-Allow-Credentialstrue

3.Access-Control-Allow-Origin *

postMessage

postMessageHTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

上面三个场景的跨域数据传递。

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递

1
otherWindow.postMessage(message, targetOrigin, [transfer]);
  • message: 将要发送到其他window的数据。
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串”*“(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

简单例子

客户端 http://127.0.0.1:5500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<!-- 等它加载完触发一个事件 -->
<!-- 内嵌在http://localhost:5500/index.html -->
<iframe src="http://localhost:8000" frameborder="0" id="frame" onload="load()"></iframe>
<p></p>
<script>
const p = document.querySelector('p');
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
window.onmessage = function (e) { //接受返回数据
console.log(e.data) //我不爱你
p.innerHTML ='客户端:'+ e.data;
}
}
</script>
</body>

服务器 http://127.0.0.1:8000

1
2
3
4
5
6
7
8
9
10
11
<body>
<p></p>
<script>
const p = document.querySelector('p');
window.onmessage = function (e) {
console.log(e.data) //我爱你
p.innerHTML = '服务器:' + e.data;
e.source.postMessage('我不爱你', e.origin)
}
</script>
</body>

WebSocket

WebSocketHTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。

WebSocketHTTP都是应用层协议,都基于 TCP 协议。

但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据

同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 clientserver 之间的双向通信就与 HTTP 无关了。

简单例子

客户端 http://127.0.0.1:5500

1
2
3
4
5
6
7
8
9
10
11
<body>
<script>
const socket = new WebSocket('ws://localhost:4000');
socket.onopen = function () {
socket.send('我爱你');//向服务器发送数据
}
socket.onmessage = function (e) {
console.log(e.data);//接收服务器返回的数据
}
</script>
</body>

服务器 http://localhost:4000

1
2
3
4
5
6
7
8
const WebSocket = require('ws');//记得安装ws
const wss = new WebSocket.Server({port:4000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
console.log(data);
ws.send(`${data}我不爱你`)
});
})

代理

简单的说,一般给客户端做代理的都是正向代理,给服务器做代理的就是反向代理。

HLcGKx.jpg

Node中间件代理

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

代理的思路为,利用服务端请求不会跨域的特性,让接口和当前站点同域。

代理服务器,需要做以下几个步骤:

  • 接收客户端请求 。
  • 将请求转发给服务器。
  • 拿到服务器响应数据。
  • 将响应转发给客户端。

React中配置代理

  • package.json中追加如下配置 :"proxy":http://localhost:5000

    • 当你请求http://localhost:5000产生跨域(本身在3000端口)时,添加此代码, 之后你请求时用http://localhost:3000进行请求,当其在3000端口中找不到资源时将会自动转发至5000端口进行请求,不产生跨域问题。

    • 优点:配置简单,前端请求资源时可以不加任何前缀。

    • 缺点:不能配置多个代理

    • 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

  • src下创建配置文件:src/setupProxy.js

    • ps:必须是这个文件名,react项目运行的时候会自动查找这个文件,并将其加入webpack的配置中,所以当你修改此文件后,你需要重新启动项目

    • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。

    • 缺点:配置繁琐,前端请求资源时必须加前缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//代码示例
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
//App.jsx
export default class App extends Component {
getStudentData = ()=>{
axios.get('http://localhost:3000/api1/students').then(
response => {console.log('成功了',response.data);},
error => {console.log('失败了',error);}
)
}

getCarData = ()=>{
axios.get('http://localhost:3000/api2/cars').then(
response => {console.log('成功了',response.data);},
error => {console.log('失败了',error);}
)
}
render() {
return (
<div>
<button onClick= {this.getStudentData}>点我获取学生数据</button>
<button onClick={this.getCarData}>点我获取汽车数据</button>
</div>
)
}
}

Nginx反向代理

测试时注意,Nginx设置了跨域后,服务器不要再设置CORS。

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

使用Nginx反向代理实现跨域,是最简单的跨域方式。只需要修改Nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

简单例子

先下载nginx,然后将nginx目录下的nginx.conf修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// proxy服务器
server {
listen 81;
server_name 127.0.0.1;

#charset koi8-r;

#access_log logs/host.access.log main;

location /nginx/ {
proxy_pass http://127.0.0.1:8000;
add_header Access-Control-Allow-Origin *;
}
}

Nginx常用命令(win)

  • cmd 进入Nginx解压目录 执行以下命令

  • start nginx : 启动nginx服务

  • 关闭cmd窗口是不能结束nginx进程的

    • 快速停止或关闭Nginx:nginx -s stop
    • 正常停止或关闭Nginx:nginx -s quit
    • 杀死所有Nginx进程:killall nginx
  • 配置文件修改重装载命令:nginx -s reload

常见问题:端口是否占用、Nginx是否结束

客户端 http://127.0.0.1:5500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<button>发送请求</button>
<div id="result"></div>
<script>
const btn = document.querySelector('button');

btn.onclick = function(){
//1. 创建对象
const x = new XMLHttpRequest();
//2. 初始化设置
x.open("GET", "http://127.0.0.1:81/nginx");
// x.open("GET", "http://127.0.0.1:8000/nginx");
//3. 发送
x.send();
//4. 绑定事件
x.onreadystatechange = function(){
if(x.readyState === 4){
if(x.status >= 200 && x.status < 300){
//输出响应体
console.log(x.response);
document.getElementById("result").innerHTML=x.response;
}
}
}
}
</script>
</body>

服务器 http://127.0.0.1:8000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1. 引入express
const express = require('express');
//2. 创建应用对象
const app = express();

//3. 创建路由规则
// request 是对请求报文的封装
// response 是对响应报文的封装

// Nginx
app.all('/nginx', (request, response)=>{
response.send('hello CORS');
});

//4. 监听端口启动服务
app.listen(8000, () => {
console.log("服务已经启动, 8000 端口监听中....");
});

window.name + iframe

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的name 值(2MB)。

其中a.htmlb.html是同域的,都是http://127.0.0.1:5500;而c.htmlhttp://127.0.0.1:8000

a.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<iframe src="http://127.0.0.1:8000" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://127.0.0.1:5500/name/b.html';
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
</body>

b.html为中间代理页,与a.html同域,内容为空。

c.html

1
2
3
<script>
window.name = '我不爱你'
</script>

总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

location.hash + iframe

实现原理: a.html欲与c.html跨域相互通信,通过中间页b.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

document.domain + iframe

该方式只能用于二级域名相同的情况下,比如 a.test.comb.test.com 适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

总结

  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
  • JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
  • 不管是Node中间件代理还是Nginx反向代理,主要是通过同源策略对服务器不加限制
  • 日常工作中,用得比较多的跨域方案是CORSNginx反向代理