websocket跨域对接问题,springboot后端和nodejs后端,vue前端

客户管理系统是springboot+vue,包含了http和websocket. springboot 端口80,vue前端端口8001,前端通过代理统一到了localhost:8001端口。

因为需要在前端播放RTSP流,采用了nodejs express+websocket+ffmpeg做后端,把RTSP流转换成flv格式传递到前端。前后端调试,可能是ws前后端跨域可能没处理好,代码如下。

以下是nodejs代码:
"use strict";
const express = require('express');
const http = require('http');
const bodyParser = require('body-parser');
const logger = require('morgan');
const path = require('path');
const colors = require('colors');
const crypto = require('crypto');
const md5 = crypto.createHash('md5');
const dbconfig = require('../dbconfig');
const mysql = require('mysql');
const RtspStream = require('rtsp-stream');
const { spawn } = require('child_process');
const ffmpeg = require('fluent-ffmpeg');
const WebSocket = require('ws');
const cors = require('cors');
const corsOptions = {origin: 'ws://localhost:8888/wim'};

const app = express();
const server = http.createServer(app);
//const wss = new WebSocket.Server({ server });
const wss = new WebSocket.Server({ server, path: '/wim', perMessageDeflate: false, handleProtocols: (protocols) => ['wim'] });

const rtspStreamUrl = 'rtsp://192.168.0.101:8554/test'; // 替换为实际的 RTSP 流地址
//const rtspStreamUrl = 'http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8';

let ffmpegCommand; // 存储 FFmpeg 进程的变量

// 处理客户端的 WebSocket 连接
wss.on('connection', (ws) => {
  console.log('wss.on+27');
  ws.on('message', (message) => {
    console.log('29');
    console.log(typeof message, message);
    //message转换成String
    if (Buffer.isBuffer(message)) {
      message = message.toString();
    };
    console.log(typeof message, message);

    if (message === 'startProxy') {
    console.log('31')
      if (ffmpegCommand) {
        // 如果已经有正在运行的 FFmpeg 进程,则结束它
        ffmpegCommand.kill();
      }
      // 创建 FFmpeg 进程,并将 RTSP 转码为 FLV
       ffmpegCommand = spawn('ffmpeg', [
         '-i', rtspStreamUrl,
         '-c:v', 'copy',
         '-c:a', 'aac',
         '-f', 'flv',
         //'-movflags', 'frag_keyframe+empty_moov',
         //'pipe:1'
       ]);

       ffmpegCommand.on('start', () => {
         console.log('FFmpeg process has started---55');
       });

       ffmpegCommand.on('error', (err) => {
         console.error('FFmpeg error:', err);
         ws.send('proxyError'); // Notify the frontend of proxy error
       });

       ffmpegCommand.on('exit', (code, signal) => {
         if (code === 0) {
           console.log('FFmpeg process has exited successfully');
         } else {
           console.error('FFmpeg process has exited with code:', code);
           ws.send('proxyError'); // Notify the frontend of proxy error
         }
       });

       ffmpegCommand.stdout.on('data', (data) => {
         // You can optionally log the FFmpeg output here
         console.log('FFmpeg output:', data.toString());
       });

       ffmpegCommand.stderr.on('data', (data) => {
         // You can optionally log the FFmpeg error output here
         console.error('FFmpeg error output:', data.toString());
       });

       ffmpegCommand.on('close', (code, signal) => {
         console.log('FFmpeg process has been closed');
         ws.send('proxyEnd'); // Notify the frontend that proxy has ended
       });

       ffmpegCommand.stdout.pipe(ws); // Pipe FFmpeg stdout to the WebSocket
      console.log('64');
      ws.send('proxyStarted'); // 通知前端代理已开始
    }
  });
});

// 静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// 处理 POST 请求中的 JSON 数据
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// 日志输出
app.use(logger('dev'));
//处理资源跨域,只允许特定域名的请求
app.use(cors(corsOptions));

//n处理客户端的 HTTP 请求
app.get('/stream', (req, res) => {
  res.setHeader('Content-Type', 'video/x-flv');
  res.send('Please use WebSocket to connect for video streaming.'); // 请使用 WebSocket 连接进行视频流传输
});

const port = 8888; // 你可以修改端口号
server.listen(port, () => {
  console.log(`服务器已启动,监听端口 ${port}`);
});


以下是前端VUE页面

<template>
  <div>
    <video ref="videoElement" controls></video>
  </div>
</template>

<script>
import flvjs from 'flv.js';

export default {
  data() {
    return {
      socket: null,
      videoElement: null,
      flvPlayer: null,
    };
  },
  mounted() {
    this.videoElement = this.$refs.videoElement;
    // 在视频播放器元素上添加点击事件


    // WebSocket代理服务器地址
    //const proxyUrl = 'ws://localhost:8001/wim';
    const proxyUrl = 'ws://' + window.location.hostname + ':8001/wim'

    // 创建WebSocket连接
    //this.socket = new WebSocket(proxyUrl);
    this.socket = new WebSocket(proxyUrl, 'wim');

    //只是为了增加一步,方便看程序
    console.log('front-end 32');

    if (this.socket.readyState === WebSocket.OPEN) {
      console.log('WebSocket 连接已经打开');
    } else {
      console.log('WebSocket 连接未打开');
    };

    this.socket.onopen = () => {
      // 在连接打开时执行的代码
      console.log('WebSocket connection opened');
      // 发送请求告诉服务器开始代理RTSP流
      this.socket.send('startProxy');
      //this.socket.send(JSON.stringify('startProxy'));
    };

if (this.socket.readyState === WebSocket.OPEN) {
      console.log('WebSocket 连接已经打开');
    } else {
      console.log('WebSocket 连接未打开');
    };

    // 当WebSocket连接打开时
    this.socket.addEventListener('open', () => {
      console.log('WebSocket connection opened');
      // 发送请求告诉服务器开始代理RTSP流
      this.socket.send('startProxy');
      //this.socket.send(JSON.stringify('startProxy'));

    });

if (this.socket.readyState === WebSocket.OPEN) {
      console.log('WebSocket 连接已经打开');
    } else {
      console.log('WebSocket 连接未打开');
    };

    // 当接收到消息时
    this.socket.addEventListener('message', (event) => {
    console.log('message39');
      const message = event.data;

      if (message === 'proxyStarted') {
        // 代理已开始,使用flv.js播放器播放视频流
        if (this.videoElement && flvjs.isSupported()) {
          this.flvPlayer = flvjs.createPlayer({
            type: 'flv',
            //url: 'http://localhost:8001/stream'
            //url: 'rtsp://192.168.0.103:8554/test'
            url: 'ws://localhost:8001/wim'
          });

          this.flvPlayer.attachMediaElement(this.videoElement);
          this.flvPlayer.load();
          this.flvPlayer.play();

          this.flvPlayer.on(flvjs.Events.ERROR, (event, data) => {
            console.error('FLV.js error:', data);
          });

          this.flvPlayer.on(flvjs.Events.LOADING_COMPLETE, () => {
            console.log('Video loading complete');
          });
        }
      } else if (message === 'proxyError') {
        // 代理发生错误
        console.error('Proxy error');
      } else if (message === 'proxyEnd') {
        // 代理已结束
        console.log('Proxy ended');
      }
    });

    // 当WebSocket连接错误时
    this.socket.addEventListener('error', () => {
      console.log('WebSocket connection Error');

    });



  },
  beforeDestroy() {
    if (this.socket) {
      // 关闭WebSocket连接
      this.socket.close();
    }

    if (this.flvPlayer) {
      // 销毁flv.js播放器
      this.flvPlayer.unload();
      this.flvPlayer.detachMediaElement();
      this.flvPlayer.destroy();
    }
  }
};
</script>

端口代理:
module.exports = {
   // 开发环境代理服务器
   devProxy: {
       host: 'localhost',//'0.0.0.0', // ip/localhost都可以访问
       port: 8001
   },
   // 后端服务器地址
   servers: {
     proxyApi: 'http://localhost:80/',
     //proxyApi: 'http://localhost:8000/',
     //ais: 'http://192.168.55.100:8888/ais',
     //ais: 'http://192.168.2.100:8000/ais',
     ais: 'http://localhost:8888/ais',
     //ais: 'http://192.168.177.181:8888/ais',
     mysqlRouter:'http://localhost:8888/mysqlRouter',
     stream: 'http://localhost:8888/stream',
     wim: 'http://localhost:8888/wim
   }
}

问题:前端页面调试,前端页面websocket看起来跑了两轮,第一轮并没有正常打开,但是代码过了一遍。第二遍nodejs后端停止运行了,前端websocket是打开的。

最后能帮向日葵远程调试

基于new bing部分指引作答:
根据您提供的代码和描述,我注意到可能存在以下问题:

跨域配置错误:在您的Node.js后端代码中,使用了cors模块来处理跨域请求,但是您定义的corsOptions中的origin值是ws://localhost:8888/wim。然而,WebSocket的跨域配置应该使用Access-Control-Allow-Origin标头来处理,而不是origin字段。您可以尝试修改corsOptions为以下内容:

const corsOptions = {
  origin: 'http://localhost:8001', // 更新为正确的Vue前端地址
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type'],
};

然后,在app.use(cors(corsOptions))之前添加以下代码:

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', corsOptions.origin);
  res.header('Access-Control-Allow-Headers', corsOptions.allowedHeaders.join(', '));
  next();
});

这样设置可以确保WebSocket连接的跨域配置正确。

调用ws.send()的时机不正确:在您的前端Vue代码中,您在WebSocket的open事件中发送了startProxy消息。但是,此时WebSocket连接可能还没有完全建立。正确的做法是在open事件之后的onopen回调中发送消息。您可以尝试修改如下:

this.socket.onopen = () => {
  // 在连接打开时执行的代码
  console.log('WebSocket connection opened');
  // 发送请求告诉服务器开始代理RTSP流
  this.socket.send('startProxy');
  //this.socket.send(JSON.stringify('startProxy'));
};

通过这种方式,您可以确保在WebSocket连接完全建立后再发送消息。

端口代理配置错误:在您提供的端口代理配置文件中,缺少了一个引号,导致wim的配置不完整。请将以下行:

wim: 'http://localhost:8888/wim

改为:

wim: 'http://localhost:8888/wim',

这样可以修正配置错误。

请尝试按照上述修改建议进行调整,看看问题是否得到解决。

WebSocket跨域和路径匹配是最关键的两点。确认前后端连接参数一致,并处理好CORS

  1. 配置express的CORS中间件:
    在Node.js后端的代码中,你已经引入了cors模块并使用了corsOptions对象作为cors中间件的配置。但是,在代码中似乎没有实际应用该中间件。你可以将app.use(cors(corsOptions));添加到express应用程序的配置部分,确保CORS中间件生效。

  2. 检查Vue前端的WebSocket地址:
    请确保前端Vue代码中使用的WebSocket地址(proxyUrl)是正确的。你已经设置为'ws://' + window.location.hostname + ':8001/wim',这将根据前端所在的域名动态生成WebSocket地址。如果你的Vue前端运行在不同的域名上,请确保你使用的URL与实际的Node.js WebSocket服务器地址匹配。

  3. 确定WebSocket连接是否成功打开:
    在Vue前端的代码中,可以通过监听WebSocket的open事件来确定WebSocket连接是否成功打开。当WebSocket连接打开时,会执行相应的回调函数,并在其中发送请求告知服务器开始代理RTSP流。你可以在websocket.onopenthis.socket.addEventListener('open', () => {})中添加日志输出,以确保WebSocket连接已成功建立。

  4. 检查网络连接和防火墙设置:
    确保Node.js后端和Vue前端之间的网络连接是可用的,并且没有防火墙或网络代理阻止WebSocket通信。你可以尝试使用其他工具或方法测试网络连接,确保能够在相同的网络环境中进行WebSocket通信。

如果上述方法不起作用,还有一些额外的调试选项和注意事项:

  • 确保Node.js后端和Vue前端代码运行在相同的域名或IP地址下,并且在相同的网络环境中。
  • 在浏览器的开发者工具中查看控制台日志和网络请求,以获取更多关于WebSocket连接失败的错误信息。
  • 在Node.js后端代码中,可以在WebSocket连接错误(error)和关闭(close)事件中添加相应的错误处理程序,以便捕获并打印出任何与WebSocket连接相关的错误。

在使用WebSocket进行跨域对接时,您需要在后端和前端进行相应的配置来允许跨域通信。下面是一种可能的解决方案:

后端(Spring Boot)配置:
在Spring Boot应用程序中,确保WebSocket配置允许跨域访问。您可以使用Spring Security或自定义配置来实现跨域请求的授权。
在Spring Boot的WebSocket配置类中,将setAllowedOrigins("*")设置为允许来自任何域的跨域请求。
确保后端服务器在响应握手请求时包含Access-Control-Allow-Origin标头,并设置为允许的前端域。
前端(Vue.js)配置:
在Vue.js应用程序中,使用WebSocket连接时,确保指定正确的后端WebSocket服务器地址。
在Vue.js的网络请求中,确保请求标头包含Origin标头,以便服务器知道请求的来源。
在Vue.js的网络请求中,使用withCredentials: true设置来发送凭据信息(如Cookie)。
确保前端服务器在响应握手请求时返回正确的CORS标头,允许WebSocket连接。
Node.js后端配置:
在Node.js后端应用程序中,使用WebSocket模块(如ws)进行配置和处理WebSocket连接。
在WebSocket服务器设置中,将origin选项设置为允许的前端域。
在WebSocket握手时,确保服务器响应包含Access-Control-Allow-Origin标头,并设置为允许的前端域。
请注意,具体的配置可能因您的应用程序和网络环境而有所不同。以上仅提供了一般性的指导方针。根据您的具体情况和需求,您可能需要根据实际情况进行调整和进一步的配置。

另外,跨域通信可能涉及到安全性和授权问题,请确保在实现跨域通信时采取适当的安全措施,以防止恶意攻击和数据泄漏。

源于chatGPT仅供参考

根据你提供的代码,有几个问题可能导致第一轮前端页面无法正常打开WebSocket连接并且后端停止运行:

1. **跨域设置**:在你的代码中,在Node.js后端的WebSocket配置中,你指定了`path`参数为`/wim`来匹配Vue前端的WebSocket连接请求。但是在前端的Vue页面中,你创建WebSocket连接时,并没有在URL中指定路径,只是指定了`ws://localhost:8001/wim`作为WebSocket的URL。请确保两端的路径匹配,可以在前端的WebSocket URL中明确指定路径。

2. **端口代理**:根据你提供的配置文件,你将前端的Vue页面代理到了`localhost:8001`端口,但是在前端的Vue页面代码中,你使用了`window.location.hostname + ':8001'`来拼接WebSocket的URL,这可能会导致WebSocket的URL不正确。建议直接使用`localhost:8001`或者将前端代理到与后端WebSocket端口号相同的端口(即8888)。

3. **WebSocket连接状态判断**:在前端的Vue页面中,你多次使用了`if (this.socket.readyState === WebSocket.OPEN)`来判断WebSocket连接是否已经打开。然而,这些判断语句都是在Vue组件的`mounted()`钩子函数中执行的,此时WebSocket的连接可能还没有完成。你应该在WebSocket的`onopen`回调函数中执行相关的操作,以确保WebSocket连接已经打开后再进行后续的操作。

尝试解决这些问题,然后重新运行代码,查看是否能够正常建立WebSocket连接并且后端不会停止运行。如果问题仍然存在,请提供错误信息或详细的日志内容,以便更好地帮助你解决问题。

不知道你这个问题是否已经解决, 如果还没有解决的话:
  • 以下回答来自chatgpt:

    问题: 请问你在前端调试过程中遇到了什么具体的问题?你期望的优化是什么?

    在前端调试过程中,我遇到的具体问题有以下几个方面:

    1. 页面加载速度过慢:当页面中的资源较多或者文件较大时,页面的加载速度会变得很慢,导致用户体验较差。 优化方案:通过压缩CSS和JavaScript文件,合并文件请求,使用CDN等方式来提升页面加载速度;

    2. 页面不兼容不同浏览器:不同浏览器对HTML、CSS、JavaScript的解析和渲染存在差异,导致页面在不同浏览器上展示效果不同。 优化方案:进行浏览器兼容性测试,使用 CSS hack 或者使用 CSS 预处理器来解决不同浏览器兼容性问题;

    3. JavaScript代码错误:由于复杂的业务逻辑或者不规范的编码导致JavaScript代码出现错误,导致页面功能无法正常运行。 优化方案:使用调试工具(如Chrome开发者工具)来定位代码错误,进行合理的异常处理和错误处理;

    4. 性能优化问题:页面的性能优化是提升用户体验的关键,包括DOM操作、网络请求、JavaScript运行等。 优化方案:减少DOM操作的次数,使用事件委托来提高性能,优化网络请求,减少不必要的请求,合理使用缓存等。

    以上问题的解决方案需要根据具体的情况进行调试和优化,通过不断地排查和测试,找到问题的根源,并进行相应的优化措施。在调试过程中,可以使用浏览器的开发者工具、调试器、日志记录工具等来辅助定位问题并加快调试的效率。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^