跨域问题?如何解决?

159 阅读4分钟

什么是跨域 ?

所谓“跨域”,指的是当一个请求试图从一个源(即协议、域名和端口的组合)访问另一个不同源的资源时所遇到的问题。浏览器为了保护用户的安全,实施了同源策略(Same-Origin Policy),该策略限制了一个源中的文档或脚本如何与另一个源的资源进行交互。同源策略的核心思想是:只有当两个URL的协议、域名和端口完全相同时,才被认为是同源。

不同源:

  1. 不同的协议

    • URL 1: http://example.com URL 2: https://example.com
    • 原因: 协议不同(http 和 https)。
  2. 不同的域名

    • URL 1: http://example.com URL 2: http://another-example.com
    • 原因: 域名不同(example.com 和 another-example.com)。
  3. 不同的端口

    • URL 1: http://example.com:80 URL 2: http://example.com:8080
    • 原因: 端口不同(80 和 8080)。

同源:http://example.com:8080http://example.com:8080/api/user

那么如何解决跨域问题呢?解决跨域问题有三种解法: JSONP、CORS、WebSocket

JSONP (JSON with Padding)

JSONP是比较古老的一种解决跨域问题的方法,它通过<script>标签不受同源限制的特点来实现跨域请求。

也就是说,我们可以在前端埋一个<script>,在里面写一个函数,让后端的返回值利用这个函数将数据传给前端就好。

比如前端是:

  function getJSONP({ url, params = {}, callback }) {
            // DOM
            return new Promise((resolve, reject) => {
                let script = document.createElement('script');
                params = { ...params, callback }
                // queryString的对象 ?callback=show&a=1&b=2
                let arr = [];
                for (key in params) {
                    arr.push(`${key}=${params[key]}`)
                }
                console.log(arr);

                // queryString 以?开始
                script.src = `${url}?${arr.join('&')}`

                window[callback] = function (data) {
                    resolve(data);
                }

                document.body.appendChild(script);
            })
        }
        getJSONP({
            url: 'http://localhost:3000/say',
            params: {
                wd: 'Iloveyou'
            },
            callback: 'show' // 必须的
        }).then(data => {
            console.log(data);

        })

我们定义这么一个方法,建立新的<script>,src为'http://localhost:3000/say?wd=Iloveyou&callback=show',将参数和调用的方法一起传给后端。在前端埋一个回调函数window[callback]来执行。

后端大概是这样:

// server.js - 原生 Node.js 版本
const http = require('http');

const server = http.createServer((req, res) => {
    // 匹配 GET 请求 /say
    // es6 字符串方法
    if (req.url.startsWith('/say')) {
        // 解析查询参数(简单处理)
        const url = new URL(req.url, `http://${req.headers.host}`);

        const wd = url.searchParams.get('wd');
        const callback = url.searchParams.get('callback'); // 解析出参数和函数名
  
        // 返回 JSONP 格式响应
        res.writeHead(200, { 'Content-Type': 'application/javascript' });
        const data = {
            code: 0,
            msg: '我不爱你'
        }
        res.end(`${callback}(${JSON.stringify(data)})`);   // 返回一个 立即执行函数,成功将数据传给前端
    } else {
        res.writeHead(404);
        res.end('Not Found');
    }
});

server.listen(3000, () => {
    console.log('Server running at http://localhost:3000');
});

哎!你看像不像React中子组件传递信息给父组件一样?前端当父亲,服务器端当儿子,前端把函数传给服务器端,服务器端进行调用,是不是很像子父组件的信息传递?🤪🤪

CORS

做到跨资源信息共享其实很简单,我们只需要在服务器端这么做:

const http = require('http');

const server = http.createServer((req, res) => {

    if (req.url === '/api/test' && req.method === 'GET') {
        res.writeHead(200, {
            'Content-type': 'application/json',
            'Access-Control-Allow-Origin': '*' // 最重要的一句
        })

        res.end(JSON.stringify({
            name: '跨域成功'
        }))

    }

})

server.listen(8080, () => {
    console.log('server is running at http://localhost:8080')
})

Access-Control-Allow-Origin的意思就是允许谁跨域访问服务器端,相当于一个白名单,在名单上的就能够成功跨域,实现服务器端资源的获取,当它的值为*时,就允许任何其他地址访问,但我们开发的时候一定不要这么做,要不然会被攻击死的55555~

一般值设置为你信任的url,比如:http://localhost/5173

WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与 HTTP 不同,WebSocket 提供了一个持久的连接,允许服务器和客户端之间进行双向的数据传输。 这使得 WebSocket 非常适合实时应用,如在线聊天、多人游戏、实时数据更新等。

前端:

// 创建一个新的 WebSocket 对象
const socket = new WebSocket('ws://example.com/socket');

// 监听连接打开事件
socket.addEventListener('open', function (event) {
    console.log('连接已打开');
    // 发送一条消息
    socket.send('Hello, Server!');
});

// 监听接收到的消息
socket.addEventListener('message', function (event) {
    console.log('收到消息:', event.data);
});

// 监听连接关闭事件
socket.addEventListener('close', function (event) {
    console.log('连接已关闭');
});

// 监听错误事件
socket.addEventListener('error', function (event) {
    console.error('发生错误:', event);
});

服务器端:

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    console.log('新连接已建立');

    // 监听接收到的消息
    ws.on('message', function incoming(message) {
        console.log('收到消息:', message);

        // 回复消息
        ws.send('Hello, Client!');
    });

    // 监听连接关闭事件
    ws.on('close', function close() {
        console.log('连接已关闭');
    });
});

WebSocket如何建立链接 ?

  1. 客户端发起 WebSocket 连接请求

    • 当客户端创建一个新的 WebSocket 对象并指定服务器的 URL 时,浏览器会发送一个 HTTP 请求到服务器。例如:

      const socket = new WebSocket('ws://example.com/socket');
      
  2. 服务器处理请求并响应

    • 服务器收到这个请求后,会检查请求头中的 Upgrade 和 Connection 字段,确认这是 WebSocket 握手请求。
    • 如果一切正常,服务器会返回一个 101 Switching Protocols 响应,并设置相应的头部信息。
  3. 连接建立

    • 客户端收到 101 响应后,会将现有的 HTTP 连接升级为 WebSocket 连接。
    • 服务器也会将连接升级为 WebSocket 连接,并触发 connection 事件。在上面的示例代码中,wss.on('connection', ...) 就是用于处理 connection 事件的回调函数。

之后服务器端就用 on事件来监听各种状态信息,利用send()来和客户端进行信息交换。

客户端也用相似的方法进行监听,也可以进行信息互换。