前言
跨域问题说实话,是个面试官就会问一嘴。如果回答的不完整或者不自信这太丢分了!为此我得好好写篇文章来梳理梳理。
同源策略
浏览器会拥有同源策略,它会阻止浏览器接收响应。请求能够发出去,但响应回不来。之所以会有同源策略,是从安全性角度来考虑的,如果没有同源策略,那么谁都可以写一个页面朝百度发送一个请求。那么后端数据毫无安全可言。 我们来分析一下http://192.168.3.1:3000/home http是协议 192.168.3.1是域名或ip地址 3000是端口号 /home是接口路径 同源策略是要求协议--域名--端口 都一致才称为同源。我们在开发过程中跨域问题是避免不了的。因此得想出一些解决方案来才行!!
解决方案
JSONP
<body>
<img src="https://img1.baidu.com/it/u=502992997"/>
</body>
图片地址放在标签上浏览器可以将图片资源加载出来。浏览器向该地址发送请求,将图片资源请求回来。这也是会跨域,但是不经过任何操作却能请求回来。这又是为啥呢?最开始同源策略极其严格,严格到任何资源只要不是同源的都请求不到,后来为了开发简单同源策略把img标签排除在外了。同理
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.4.38/vue.cjs.js"></script>
这段源码会被加载出,但这种现在缺点很明显后端返回的响应体根本操作不了。那么我们又该怎么办呢?
function jsonp(url,cb){
return new Promise((resolve,reject)=>{
const script =document.createElement('script');
window[cb]=function(data){//function callback(){}
}
script.src=`${url}?cb=${cb}`;
document.body.appendChild(script)
})
}
jsonp('http://localhost:3000','callback').then(res=>{
console.log(res)
})
const http=require('http');
http.createServer(function(req,res){
const query=new URL(req.url,`http://${req.headers.host}`).searchParms//截取路径参数的方法
console.log(query.get('cb'));
if(query.get('cb')){
const cb=query.get('cb')//'callback'
const data='hello world'
const result=`${cb}("${data}")` //'callback(""hello world")'
res.end(result)
}
res.end('hello world')
}).listen(3000)
好啦现在一句话总结下JSOP的原理:借助script 标签的scr属性不受同源策略的影响,来发送请求。给后端携带一个参数 callback 并在前端定义callback函数体,后端返回callback的调用形式并将要响应的值作为callback的实参,当浏览器接收到响应后就会触发全局的callback函数从而让callback以参数的形式接收到后端响应。 缺点:需要后端配合,并且只能发送get请求。
cors
我们先看看下边的代码
const xhr =new XMLHttpRequest();
xhr.open('GET','http://localhost:3000')
xhr.send();
xhr.onreadystatechange=function(){
if(xhr.readyState===4 && xhr.status===200){
console.log(xhr.responseText);
}
}
const http=require('http');
http.createServer(function(req,res){
res.writeHead(200,{
'Access-Control-Allow-Origin':'*'
'access-control-allow-methods':'GET' //前端只允许发GET要不然还是会被跨域
})
res.end('hello world')
}).listen(3000)
好啦以上代码可以一句话总结出:通过设置响应头中的某些字段:比如,access-control-allow-origin 设置允许的源来通知浏览器此时同源策略不需要生效。
proxy
const xhr=new XMLHttpRequest();
xhr.open('GET','http://localhost:3001')
xhr.send();
xhr.onreadystatechange=function(){
if(xhr.readyState===4&&xhr.status===200){
console.log(xhr.responseText);
}
}
const http=require('http');
http.createServer(function(req,res){
res.writeHead(200,{
"access-contol-allow-origin":"*"
})
http.request({
host:'192.168.1.63'
port:'3000',
path:'/',
method:'GET',
headers:{}
},proxyRes=>{
proxyRes.on('data',function(data){
res.end(data.toString())
})
}).end()
}).listen(3000);
其原理可以有以下总结:
代理服务器的工作原理
-
客户端请求:客户端向代理服务器发送请求。
-
代理服务器转发请求:代理服务器接收请求后,将其转发到目标服务器。
-
目标服务器响应:目标服务器处理请求并返回响应。
-
代理服务器转发响应:代理服务器接收目标服务器的响应,并将其转发回客户端。
这种方法原理我们也可以用在vue项目中的vite.config.js文件里边配置。
server:{
proxy:{
'/api':{
target:'http://ustbhuangyi.com/sell/',
changeOrigin:true,
rewrite:(path)=>path.replace(/^\/api/,'/api')
}
}
其实在vite里边配置这个对象就相当于在启动前边所演示的后端项目。但是如果上线的话,把项目打包部署到服务器上去的话这段代码就不会存在了,vite只是一个构建工具。vite的源码是不在的,所以只是在开发过程中有用,部署后就没用了。
nginx
nginx代理的原理跟node代理是一样的,相当于一个node代理。服务器上有一个前端项目,和后端项目。我们可以在服务器上装一个nginx(工具软件)我们在配置文件里配置前端地址,后端地址当后端返回了一个响应由nginx接收到nginx就相当于启动了node服务一样再返回给前端。总结:.Nginx 解决跨域问题的过程是通过配置反向代理,将客户端请求转发到目标服务器,并在响应中添加必要的 CORS 头,从而使浏览器允许跨域请求。
WebSocket
前边介绍的几种都是很常见的,接下来我们来看看WebSocket吧!!Socket和http一样都是用来进行超文本传输的,但是http与Socket有一个非常大的区别,http是一个请求发过去,一个请求回来就算结束了,Socket则不然一旦建立了连接就不会断开了,除非人为!!
<script>
function myWebSocket(url,parms={}){
return new Promise(function(resolve,reject){
const socket=new webSocket(url)
socket.onopen=()=>{
socket.send(JSON.stringify(parms))
}
socket.onmessage=function(e){
console.log(e.data);
}
})
}
myWebSocket('ws://localhost:3000',{age:18}).then(res=>{
consloe.log(res);
})
</script>
const WebSocket=require('ws');
const ws=new WebSocket.Server({port:3000});
let count=0
ws.on('connection',function(obj){
obj.on('message',function(data){
obj.send('哈哈')
setInterval(()=>{
obj.send(++count)
},2000)
})
})
总结:socket 协议天生不受同源策略的影响。
postMessage
当页面一通过iframe嵌套了页面二,这两个页面上已经无法进行通讯的,因为跨域了,但是使用postMessage可以实现跨域
document.domain
两个页面通过iframe进行嵌套,且两个页面的二级域名是一致的!!