距离上次写文章已经是在上次了,历经了两三个星期的面试,感觉自己在逐步提升,身边的小伙伴也成功拿下了字节,在替他高兴的同时也时刻告诫自己是时候该冲刺了。今天主要分享的是面试中常问的一道题:如何实现跨域。
同源策略
浏览器为了保证用户的隐私和数据安全,实施了一种称为“同源策略”的安全机制。这意味着,只有当请求的来源(包括协议、域名和端口)与请求的目标完全一致时,才能获取到目标资源。
例如:https://192.168.3.0:3000/home
其中,https便是协议,192.168.3.0是域名,3000是端口,之后的便是路径
同源策略(Same-origin policy)是一种约定俗成的安全措施,旨在防止恶意脚本从一个站点读取另一个站点的数据。这项政策确保了只有来自同一源的请求才能访问相应的资源,而不同源的请求则被浏览器拦截。因此,对于需要从不同源加载资源或数据的应用来说,必须采取额外的技术手段来突破这一限制。
跨域解决方案
JSONP
<img src="https://img2.baidu.com/it/u=1490602810,3032519025&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1724432400&t=07e35fb47df14aae4c5426372d8dc268"
alt="">
首先,我们发现对于img中的src是不受同源策略的影响,JSONP也正好借助的是这一点,它利用了 <script> 标签的 src 属性不受同源策略限制的特点,前端可以通过设置 src 属性发起GET请求。
<script>
function jsonp(url, cb) {
return new Promise(function (resolve, reject) {
const script = document.createElement('script');
window[cb] = function (data) { // callback()
resolve(data)
}
script.src = `${url}?cb=${cb}`;
document.body.appendChild(script);
});
}
jsonp('http://localhost:3000', 'callback').then(res => {
console.log(res);
})
</script>
在src中,我们携带callback函数,并在windows全局添加它。
const http = require('http');
http.createServer(function (req, res) {
const query = new URL(req.url, `http://${req.headers.host}`).searchParams
if (query.get('cb')) {
const cb = query.get('cb') // 'callback'
const data = 'hello world 12312312123'
const result = `${cb}("${data}")` // 'callback("hello world")'
res.end(result)
}
}).listen(3000);
其中query拿到的是存储callback的map对象,后端接收到请求后,如果存在cb的话,将需要返回给前端的数据作为callback的参数一起以字符串的形式传给前端。我们之前在windows全局添加了该方法,所以在windows环境中会将该字符串读成callback函数,直接执行拿到数据。
CORS
const http = require('http');
http.createServer(function (req, res) {
//开启cors
res.writeHead(200,{
//允许的源
'Access-Control-Allow-Origin': '*'//'http://localhost:5500'
})
res.end('Hello world');
}).listen(3000);
CORS(Cross-Origin Resource Sharing)是另一种更为强大和灵活的跨域解决方案。它通过在HTTP响应头中添加特定的CORS头信息(如 Access-Control-Allow-Origin),其中的*代表允许任何形式的源访问资源。CORS支持各种HTTP方法和数据类型,可以根据具体的场景设置条件允许其访问。
WebSocket
<script>
function web_Socket(url,params={}) {
return new Promise((resolve) => {
const socket = new WebSocket(url)
socket.onopen = () => {
socket.send(JSON.stringify(params))
}
socket.onmessage = (res) => {
resolve(res.data)
}
})
}
web_Socket('http://localhost:3000').then(data => {
console.log(data);
})
</script>
WebSocket是一种全双工通信协议,它提供了一个持久连接,可以在客户端和服务端之间进行双向数据传输。在通讯连接成功后onopen会将params对象转换为字符串的形式传递给后端,当WebSocket接收到消息时onmessage触发,这里它将接收到的消息数据解析并调用 resolve 函数来解析Promise。
const WebSocket = require('ws');
const ws = new WebSocket.Server({ port: 3000 });
let count=1;
ws.on('connection',(obj)=>{
obj.on('message',(data)=>{
// obj.send('this is a message');
setInterval(()=>{
count++
obj.send(count)
},2000)
})
})
在双向通信连接成功后,服务器有新的信息就会向前端推送。
WebSocket协议本身不受同源策略的限制,非常适合需要实时双向通信的应用场景。不过,它的实现相对复杂,且需要服务器端的支持。
postMessage
HTML5引入了 postMessage API,使得不同源的窗口对象之间可以进行消息传递。这对于父窗口与子窗口(如iframe)之间的通信非常有用。尽管 postMessage 提供了基本的跨域通信能力,但由于其功能较为有限,可能不适用于所有场景。
document.domain
对于拥有相同顶级域名的不同子域,可以通过设置 document.domain 来实现跨子域通信。虽然这种方法可以简化某些情况下的跨域问题,但它也有明显的局限性,例如不能跨越不同的顶级域名,并且在HTTPS环境下可能受到限制。
Nginx代理
在服务器端使用Nginx作为反向代理,通过配置Nginx来转发请求并处理响应。这种方式可以绕过浏览器的同源策略限制,特别适合于那些后端有权限修改服务器配置的情况。通过Nginx代理,可以实现更高级别的控制,并且有助于提高系统的整体性能。
结论
综上所述,跨域问题是Web开发中不可避免的一个挑战。不同的解决方案各有千秋,选择最合适的方案取决于具体的应用场景和技术栈。无论是使用简单的JSONP,还是复杂的WebSocket,或者是介于两者之间的CORS,开发者都应该综合考虑其适用性和安全性,以确保既能满足业务需求又能保障系统的安全性。