Commit 695ab490 authored by zhangshun's avatar zhangshun
parents e8b5d67c 753a10d4
---
title: WebSocket
date: 2022/7/29 15:00:00
tags:
- WebSocket
author: 张芳利
---
......
---
title: 浅谈跨域
date: 2022/8/30 10:54:30
tags:
- JSONP
- postMessage
- WebSocket
author: 张芳利
---
<a name="XDoI3"></a>
## 一、什么是跨域
要知道什么是跨域,需要先知道另一个名词【**同源政策**】。<br />同源政策:是一个重要的安全政策,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。<br />而什么才是同源呢?同源就是两个页面具有相同的协议,域名和端口 均一致。所以根据上面的了解,**源=协议+主机/域名+端口,当源不同的时候也就是跨域了**<br />例如:<br />
![crossDomain](/yifu-study-front-share/images/crossDomain/img1.png)
| **当前页面url** | **被请求页面url** | **是否跨域** | **原因** |
| --- | --- | --- | --- |
| http://www.test.com/ | http://www.test.com/index.html | 否 | 同源 |
| http://www.test.com/ | https://www.test.com/index.html | 是 | 协议不同(http/https) |
| http://www.test.com/ | http://www.baidu.com/ | 是 | 主域名不同(test/baidu) |
| http://www.test.com/ | http://blog.test.com/ | 是 | 子域名不同(www/blog) |
| http://www.test.com:8080 | http://www.test.com:7001/ | 是 | 端口号不同(8080/7001) |
**注意:**
> 在默认情况下 http 可以省略端口 80, https 省略 443。
<a name="C9fUE"></a>
## 二、跨域的限制
- Cookie,LocalStorage,IndexDB 等存储性内容无法读取
- DOM 节点无法访问
- Ajax 请求发出去了,但是响应被浏览器拦截了
基本我们说的跨域是在第三点请求上面。<br />ajax请求报错:
```jsx
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been block by CORS,
policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```
<a name="HSQ39"></a>
## 三、解决跨域
<a name="uTL8Z"></a>
### 方法一:JSONP
JSONP 是 JSON with Padding 的缩写,主要就是利用了 script 标签没有跨域限制的这个特性来完成的。<br />**思路:网页通过添加一个<script>元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。**<br />缺点:只支持get请求,不支持post请求。<br />(下面的接口都是聚合数据里面的免费接口,如果想实验测试一下可以自己更换一下接口地址哈~)<br />**1、原生js**
```jsx
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
//或
<script type="text/javascript">
window.jsonpCallback = function(res) {
console.log(res);
};
script>
<script
src="http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback"
type="text/javascript"
>script>
```
**2、vue.js ** <br />2.1 安装jsonp,利用jsonp插件
```jsx
yarn add jsonp
import jsonp from "jsonp";
// 这里的url 是聚合免费api
export const getTest = () => {
jsonp('http://v.juhe.cn/joke/content/list.php?key=79255ba6ab74a79d35edb174142c309c&page=2&pagesize=10&sort=asc&time=1418745237',{},(err,data)=>{
console.log(err)
console.log(data)
})
}
```
![crossDomain](/yifu-study-front-share/images/crossDomain/img2.png)
2.2 自己封装一下jsonp 函数方法
```jsx
const jsonpFun = ({ url, params })=>{
return new Promise((resolve, reject) => {
// 创建一个临时的 script 标签用于发起请求
const script = document.createElement('script');
// 将回调函数临时绑定到 window 对象,回调函数执行完成后,移除 script 标签
window['jsonCallBack'] =(result) => {
resolve(result)
document.body.removeChild(script);
}
// 构造 GET 请求参数,
const formatParams = { ...params, callback: 'jsonCallBack' };
const requestParams = Object.keys(formatParams)
.reduce((acc, cur) => {
return acc.concat([`${cur}=${formatParams[cur]}`]);
}, [])
.join('&');
// 构造 GET 请求的 url 地址
const src = `${url}?${requestParams}`;
script.setAttribute('src', src);
script.setAttribute('type','text/javascript');
document.body.appendChild(script);
});
}
// 使用
export const getTest = () => {
jsonpFun({
url:'http://v.juhe.cn/joke/content/list.php',
params:{
key:'79255ba6ab74a79d35edb174142c309c',
page:2,
pagesize:10,
sort:'asc',
time:1418745237
}
}).then(res=>{
console.log(res);
})
}
```
2.3 给axios 上面拓展一个jsonp
```jsx
// 给axios添加jsonp请求
axios.jsonp = (url) => {
if(!url){
console.error('Axios.JSONP 至少需要一个url参数!')
return;
}
return new Promise((resolve,reject) => {
window.jsonCallBack =(result) => {
resolve(result)
}
var JSONP=document.createElement("script");
JSONP.type="text/javascript";
JSONP.src=`${url}&callback=jsonCallBack`;
document.getElementsByTagName("head")[0].appendChild(JSONP);
setTimeout(() => {
document.getElementsByTagName("head")[0].removeChild(JSONP)
},500)
})
}
// 使用
export const getTest = () => {
axios.jsonp('http://v.juhe.cn/joke/content/list.php?key=79255ba6ab74a79d35edb174142c309c&page=2&pagesize=10&sort=asc&time=1418745237').then(res=>{
console.log(res);
})
}
```
<a name="LNDy8"></a>
### 方法二:CORS
CORS是基于http1.1的一种跨域解决方案,它的全称是**C**ross-**O**rigin **R**esource **S**haring,跨域资源共享。<br />**思路:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许**<br />缺点:当你使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,就不适合 <br />在 cors 中会有 简单请求 和 复杂请求的概念。简单请求:当请求**同时满足**以下条件时,浏览器会认为它是一个简单请求:<br />情况一: 使用以下方法(意思就是以下请求意外的都是非简单请求)
- GET
- HEAD
- POST
情况二: 人为设置以下集合外的请求头
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
情况三:Content-Type的值仅限于下列三者之一:(例如 application/json 为非简单请求)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
情况四:<br />请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。<br />情况五:<br />请求中没有使用 ReadableStream 对象。
```jsx
// 简单请求
fetch('http://crossdomain.com/api/news');
// 请求方法不满足要求,不是简单请求
fetch('http://crossdomain.com/api/news', {
method: 'PUT',
});
// 加入了额外的请求头,不是简单请求
fetch('http://crossdomain.com/api/news', {
headers: {
a: 1,
},
});
// 简单请求
fetch('http://crossdomain.com/api/news', {
method: 'post',
});
// content-type不满足要求,不是简单请求
fetch('http://crossdomain.com/api/news', {
method: 'post',
headers: {
'content-type': 'application/json',
},
});
```
不满足上面的就是复杂请求。一般CORS的设置都是在后端的。
**1、**普通跨域请求:只需服务器端设置Access-Control-Allow-Origin<br />2、带cookie跨域请求:前后端都需要进行设置<br />**【前端设置】根据xhr.withCredentials字段判断是否带有cookie**
```jsx
// 原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
//jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
data: {},
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
});
// vue-resource
Vue.http.options.credentials = true
// axios
axios.defaults.withCredentials = true
```
<a name="EBefT"></a>
### 方法三:代理
**思路:利用服务端请求不会跨域的特性,让接口和当前站点同域。**<br />代理适用的场景是:生产环境不发生跨域,但开发环境发生跨域。<br />
![crossDomain](/yifu-study-front-share/images/crossDomain/img3.png)
像是vue中,一般是这么处理。现在常用的vue等框架都有解决方案。
```jsx
// 在webpack中可以配置proxy来快速获得接口代理的能力。
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
},
// Vue-cli 2.x
// config/index.js
...
proxyTable: {
'/api': {
target: 'http://localhost:8080',
}
},
...
// Vue-cli 3.x
module.exports = {
devServer: {
port: 8000,
proxy: {
"/api": {
target: "http://localhost:8080"
}
}
}
};
// 等等
```
<a name="qs5RQ"></a>
### 方法四:Nginx 反向代理
Nginx 实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。<br />使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。<br />主要是后端进行nginx配置,就不讲了。
<a name="OoScp"></a>
### 方法五:websocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。什么是全双工通信 ?简单来说,就是在建立连接之后,server 与 client 都能主动向对方发送或接收数据。<br />以第**三方库 ws** 为例:
```jsx
........
const WebSocket = require('ws');
const ws = new WebSocket('ws://www.host.com/path');
ws.on('open', function open() {
ws.send('something');
});
ws.on('message', function incoming(data) {
console.log(data);
});
... ...
```
<a name="BuN2e"></a>
### 方法六:window.postMessage()
此方法可以安全地实现跨源通信。window.postMessage() 方法允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本,而不用管是否跨域,可以用这种消息传递技术来实现安全的通信。这项技术称为“**跨文档消息传递**”,又称为“**窗口间消息传递**”或者“**跨域消息传递**”。<br />它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
**发送消息**
```jsx
otherWindow.postMessage(message, targetOrigin, [transfer]);
```
> **otherWindow**:其他窗口的一个引用。比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
> message:要发送的消息。它将会被结构化克隆算法序列化,所以无需自己序列化,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化。
> **targetOrigin:**“目标域“。URI(包括:协议、主机地址、端口号)。若指定为”*“,则表示可以传递给任意窗口,指定为”/“,则表示和当前窗口的同源窗口。当为URI时,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送。
**接收消息**<br />如果指定的源匹配的话,那么当调用 postMessage() 方法的时候,在目标窗口的Window对象上就会触发一个 message 事件。<br />获取postMessage传来的消息:为页面添加onmessage事件。
```jsx
window.addEventListener('message',function(e) {
var origin = event.origin;
// 通常,onmessage()事件处理程序应当首先检测其中的origin属性,忽略来自未知源的消息
if (origin !== "http://example.org:8080") return;
// ...
}, false)
```
> event 的属性有:
> data: 从其他 window 传递过来的数据副本。
> origin: 调用 postMessage 时,消息发送窗口的 origin。例如:“http://example.com:8080”。
> source: 对发送消息的窗口对象的引用。可以使用此来在具有不同 origin 的两个窗口之间建立双向数据通信。
例如:
1. vue项目中,页面http://localhost:4000/=>http://localhost:4000/created 传数据
```jsx
// http://localhost:4000/
const gtoSend = () => {
// 发送
const targetWindow = window.open('http://localhost:4000/created');
setTimeout(()=>{
targetWindow.postMessage('你好,postMessage', 'http://localhost:4000/')
},100)
}
onMounted(() => {
window.addEventListener('message', (e) => {
console.log(e.data) //我收到信息了
})
})
//http://localhost:4000/created
// 接收
onMounted(() => {
window.addEventListener('message', (e) => {
console.log(e)
console.log(e.source); // e.source 发送消息的窗口 http://localhost:4000/
console.log(e.origin); // e.origin 消息发向的网址 http://localhost:4000/created
console.log(e.data); // e.data 发送的消息 你好,postMessage'
test.value = e.data
if (e.origin !== "http://localhost:4000") return;
e.source.postMessage('我收到消息了', e.origin)
},false)
})
```
2.iframe 窗口
```jsx
// http://localhost:4000/
<template>
<div class="home">
<button @click="changeFor" style="margin-top:20px">请求</button>
<div style="width:120%">
<iframe id="iframe"
src="http://localhost:4000/created"
style="width:100%;height:300px">
</iframe>
</div>
</template>
<script>
onMounted(() => {
window.addEventListener('message', (e) => {
console.log('4000页面',e.data) //4000页面 你好,我是created页面
})
})
var iframe = document.getElementById('iframe');
const changeFor = () => {
window.frames[0].postMessage('来首页的消息的消息', 'http://localhost:4000/created');
}
</script>
//http://localhost:4000/created
onMounted(() => {
window.addEventListener('message', (e) => {
console.log('created页面',e)
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
if (e.origin !== "http://localhost:4000") return;
e.source.postMessage('你好,我是created页面', e.origin)
},false)
})
```
![crossDomain](/yifu-study-front-share/images/crossDomain/img4.png)
其他方法暂时不提了,基本常用的就是上面6种了。💯
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment