什么是跨域
跨域是当浏览器中一个网页向另一个不同源的资源发送请求时,请求会被拦截,而所谓同源则是在一个 HTTP 地址中,他们的协议,域名,端口号都要一致,下面我们要详细对如何去处理快于问题来进行讨论
同源策略
浏览器出于对自身数据安全,服务器安全(减少被 xss(跨站脚本攻击) , CSRF(请求伪造) 攻击),只有两个 HTTP 地址中的,协议,域名和端口号完全一致才会识别为同源,而非同源请求发送后浏览器则会拦截响应
跨域方案
- jsonp
该方案是早期为了解决跨域问题的一个取巧方式,在<script>是不会受同源策略的影响,从而动态在<script>标签下发送请求
实现原理
-
通过在前端的url后携带一个
callback参数传递给后端(此过程中前端在全局下定义了一个callback函数体来接受后端返回的callback函数的调用内容) -
后端接收
callback并将数据作为callback的实参返回前端一个callback的调用形式 -
浏览器接收到
callback的调用会自动执行全局callback函数
特点
- 必须要前后端配合
- 只能发送get 请求
- 容易遭受到XSS 攻击
示例代码
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- <link rel="stylesheet" href='http://xxxx.css'> -->
</head>
<body>
<button onclick="handle()">请求</button>
<script>
function jsonp(url,cb){
return new Promise((resolve,reject)=>{
const script= document.createElement('script')
window[cb] = function(data){
// console.log(data);//返回后端数据
resolve(data)
}
script.src = `${url}?cb=${cb}`
document.body.appendChild(script)
// callback('hello world')
})
}
function handle(){
jsonp('http://localhost:3000','callback').then(res => {
console.log(res);
})
}
</script>
</body>
</html>
后端代码
const http = require('http')
http.createServer((req, res) => {
const query = new URL(req.url,`http://${req.headers.host}`).searchParams
// console.log(query.get('cb'));
if(query.get('cb')){
const cb = query.get('cb') //'callback'
const data ='hello world'
const resulet = `${cb}("${data}")` // "callback('hello world')"
res.end(resulet)
}
// res.end('hello world')
}).listen(3000)
CORS (跨域资源共享):
CORS 是一种跨域访问的机制,它通过对后端请求头中设置一份白名单来接受浏览器上不同域名能够来访问后端资源
实现原理
后端设置 响应头 Access-Control-Allow-Origin: '域名白名单',来通知浏览器哪些域名可以跨域访问
示例代码
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="handle()">请求</button>
<script>
function handle() {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://localhost:3000', true)
xhr.send()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
}
</script>
</body>
</html>
后端代码
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200, {
'access-control-allow-origin': 'http://127.0.0.1:5500', // 允许某个域名跨域
'access-control-allow-methods': 'GET,POST,PUT,DELETE', // 允许的请求方式
'access-control-allow-headers': 'x-requested-with,content-type' // 允许的请求头
})
res.end('Hello World')
})
server.listen(3000)
nginx 反向代理
- 实现原理:中间构造一个后端,在后端中允许前端跨域,从而接受前端内容,并将此内容发送到后端服务器中(不跨域--没有同源策略(是服务器不是浏览器)),从而在将后端资源依次返回至前端。
node 中间件代理
- 前端服务器和后端服务器不在同一个域名下,前端服务器通过node 中间件来访问后端服务器这个方法其实和上面的
nginx 反向代理大差不差,只是换了一种语言写而已。
websocket
- 我们在传统的通讯方式:基于 HTTP 协议的,是单向的,只能从一端发送到另一端,无法反向通信而我们现在介绍的
websocket是基于tcp 协议的,可以从一端发送到另一端,也可以从另一端发送到一端,我们就可以把它理解为是我们平时用的微信通话,双方都能都进行交互
特点
-
是双向的,可以从一端发送到另一端,也可以从另一端发送到一端
-
socket 协议一旦建立链接,就可以一直保持通信状态, 不需要每次建立链接
-
天生就可以跨域
示例代码
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function WebSocketTest(url, params = {}) {
return new Promise((resolve, reject) => {
const socket = new WebSocket(url)
socket.onopen = () => {
socket.send(JSON.stringify(params))
}
socket.onmessage = (event) => {
console.log(event.data);
resolve(event.data)
}
})
}
WebSocketTest('ws://localhost:3000', {age: 18}).then(res => {
console.log(res)
})
</script>
</body>
</html>
后端代码
const WebSocket = require ('ws')
//在 3000 端口上建立 websocket 伺服务(随时都在线的服务)
const ws = new WebSocket.Server({port:3000})
let count = 0
ws.on('connection',(obj)=>{
// console.log(obj);
obj.on('message',(msg)=>{
// console.log(msg.toString());
obj.send('收到了')
setInterval(()=>{
count++
obj.send(count)
},2000)
})
})
postMessage
我们在日常提及跨域问题都是出现在前端与后端之间的交互所产生的,其实在前端也能够实现彼此不同页面之间的数据传输,那就要先了解一下iframe标签,它可以将一个页面嵌入到父级页面当中去,让我们先来看一下它在html实现示例,将我们最喜欢的bilibili大学网站嵌入到父页面中
代码示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>首页</h2>
<iframe src="http://www.bilibili.com/" frameborder="0" width="800" height="500"></iframe>
</body>
</html>
那我们不难理解当我们这样使用
iframe标签时,当我们要将两个页面的数据资源进行交互同样也会产生跨域问题。那为了处理此问题我们就要通过接下来的主角postMessage来对此跨域进行处理
代码示例
父页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>首页</h2>
<iframe id="frame" src="http://127.0.0.1:5500/%E8%B7%A8%E5%9F%9F/postMessage/detail.html" frameborder="0" width="800" height="500"></iframe>
<script>
let obj ={name:'zyx',age:'18'}
document.getElementById('frame').onload = function() {
this.contentWindow.postMessage(obj,'http://127.0.0.1:5500')//向iframe发送消息
window.onmessage = function(e) { //接收iframe发送的消息
console.log(e.data);
}
}
</script>
</body>
</html>
iframe 页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>首页</h2>
<iframe id="frame" src="http://127.0.0.1:5500/%E8%B7%A8%E5%9F%9F/postMessage/detail.html" frameborder="0" width="800" height="500"></iframe>
<script>
let obj ={name:'zyx',age:'18'}
document.getElementById('frame').onload = function() {
this.contentWindow.postMessage(obj,'http://127.0.0.1:5500')//向iframe发送消息
window.onmessage = function(e) { //接收iframe发送的消息
console.log(e.data);
}
}
</script>
</body>
</html>
document.domain
通过设置document.domain来允许同一个域名下的跨域通信,原理同postMessage一样,但谷歌禁止了这种方法,这里就不再展示了
总结
我们在日常处理跨域问题共有以下七种方法
- jsonp
- CORS (跨域资源共享)
- nginx 反向代理
- node 中间件代理
- websocket
- postMessage
- document.domain