Vue项目配置WebSocket连接{ws、wss 连接方式}配置

Vue项目配置WebSocket连接 ws、wss 两种方式

1. 写作背景

  • 项目使用的是ruoyi的前后端分离框架
  • 项目需要使用到 websocket , 在本地使用 ws 连接方式是没问题 , 但是服务器上边使用的是nginx + ssl 证书 https域名访问的方式部署的 使用普通的 ws 连接是不可以成功的 需要使用 wss的方式

2. 晒出代码

2.1 前端 vue.config.js 的代码

这里target: 里边指向的都是后端server的地址 16000是我后端服务的端口 , 我这里websocket服务和普通的业务项目用的都是一个项目 所以都是16000端口

  devServer: {
    host: '0.0.0.0',
    port: port,
    open: true,
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      // 正常的 http 请求代理
      [process.env.VUE_APP_BASE_API]: {
        target: `http://localhost:16000`,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      },
      // websocket ws 的代理路由配置
      [process.env.VUE_APP_WEBSOCKET_API]: {
        target: `ws://localhost:16000`,
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_WEBSOCKET_API]: ''
        }
      },
      // websocket wss 的代理路由配置
      [process.env.VUE_APP_WSS_WEBSOCKET_API]: {
        target: `wss://域名:16000`,
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_WSS_WEBSOCKET_API]: ''
        }
      }
    },
    disableHostCheck: true
  },
2.2 Vue项目路由配置代码

为什么要配置两个地址呢? 

因为在本次测试的时候使用的是普通的ws方式连接 所以为了方便切换就写了两个websocket代理路由

 env.development 文件和 .env.production 文件都加上这两行代码即可

//  WebSocket地址
VUE_APP_WEBSOCKET_API = '/websocket-api'
// WebSocket wss 地址
VUE_APP_WSS_WEBSOCKET_API = '/wss-websocket-api'
3.3 服务器Nginx配置
server {
		add_header X-Frame-Options ALLOWALL;
        listen       8681 ssl;
		server_name 域名; #需要将yourdomain.com替换成证书绑定的域名。
		root ..\html;
		index index.html index.htm;
		ssl_certificate pem文件地址;  #需要将cert-file-name.pem替换成已上传的证书文件的名称。
		ssl_certificate_key文件地址; #需要将cert-file-name.key替换成已上传的证书私钥文件的名称。
		ssl_session_timeout 6m;
		ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
		ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #表示使用的TLS协议的类型。
		ssl_prefer_server_ciphers on;
        #charset koi8-r;

        access_log  logs/host.access.log  main;

		#默认目录
		location / {
            root   C:/xxx/dist;
            index  index.html;
			try_files $uri $uri/ @router;
        }
		location @router {
            rewrite ^.*$ /index.html last;
        }

		#vue二级目录代理
		location /admin {
            alias /root/www/admin;
			index  index.html;
            try_files $uri $uri/ /index.html last;
        }

		location /prod-api {
		 	rewrite  ^/prod-api/(.*)$ /$1 break;
			proxy_pass http://localhost:16000;
			proxy_set_header Host $host;
			add_header X-Frame-Options ALLOWALL;
			proxy_set_header User-Agent $http_user_agent;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $remote_addr;
			proxy_set_header authorization $http_authorization;
		}
		# websocket  wss 连接方式的路由代理配置
		location /wss-websocket-api {
			 rewrite  ^/wss-websocket-api/(.*)$ /$1 break;
			 proxy_pass http://localhost:16000;        #通过配置端口指向部署websocker的项目
			 proxy_http_version 1.1;
			 proxy_set_header Upgrade $http_upgrade;    
			 proxy_set_header Connection "Upgrade";    
			 proxy_set_header X-real-ip $remote_addr;
			 proxy_set_header X-Forwarded-For $remote_addr;
		 }
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

3. 使用方式

3.1 前端代码
// 当前浏览器Location对象
const nowLocation = window.location;
// 协议 => http、https
const protocol = nowLocation.protocol;
// hostName => ip
const hostName = nowLocation.hostname;
// host => ip:port
const host = nowLocation.host;
// websocket api 地址
// 这个判断就是根据当前项目环境 自动确定使用 ws 还是 wss 的路由地址
const websocket_pattern = (hostName == '域名') ? 'wss-websocket-api' : 'websocket-api';

// websocket 请求地址前缀
const webSocketApiUrl =  ((protocol.startsWith('https')) ? 'wss://' : 'ws://') + host + '/' + websocket_pattern;


// 当前WebSocket的请求地址前缀,
// /websocket/template-push/ 就是我后端配置的websocket端点地址
let REQUEST_WEBSOCKET_URL_PREFIX = webSocketApiUrl + '/websocket/template-push/';
// 当前的WwebSocket对象
let CURRENT_SOCKET = null;
// 当前请求WebSocket的指令代码
let CURRENT_INDICATE_CODE = null;


let ENABLE_CONFIG = {

  WEBSOCKET_PUSH_VIDEO_ENABLE: true,
}

/**
 * 1. 初始化WebSocket连接对象
 * @param {*} clientKey 当前客户端Key
 */
function openWebSocket(clientKey) {
	if (CURRENT_SOCKET != null) {
		CURRENT_SOCKET.close();
		CURRENT_SOCKET = null;
	}

	CURRENT_SOCKET = new WebSocket(REQUEST_WEBSOCKET_URL_PREFIX + clientKey);

	CURRENT_SOCKET.onopen = event => {
		console.log('连接Socket');
	};

	// 从服务器接受到信息时的回调函数
	CURRENT_SOCKET.onmessage = event => {
		console.log('收到服务器响应 , 响应数据信息==>' , event.data);
	};

	CURRENT_SOCKET.onclose = event => {
		console.log('关闭Socket连接!');
	};

	//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
	window.onbeforeunload = () => {
		CURRENT_SOCKET.close();
		CURRENT_SOCKET = null;
	};
}

function getWebSocketConnection() {
	return CURRENT_SOCKET;
}

 前端websocket向后端发送数据使用方式

let sendData = {};
getWebSocketConnection().send(JSON.stringify(sendData));
 3.2 后端代码
package com.ruoyi.web.controller.websocket;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.websocket.WebSocketClientIndicate;
import com.ruoyi.websocket.WebSocketRequest;
import com.ruoyi.websocket.WebSocketTemplateSession;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

/**
 * --->
 *
 * @author xqh , 987072248@qq.com
 * @data 2023-11-02 15:46:51
 */
@Component
@ServerEndpoint("/websocket/template-push/{clientKey}")
@RequiredArgsConstructor
public class WebSocketTemplateInfoPushServer {

    /**
     * 统计在线人数 线程安全的计数器 比原子更新类 效率更高 更专业
     */
    private final static LongAdder ONLINE_ADDER = new LongAdder();
    /**
     * 客户端 连接会话存储Map , 每个客户端对应一个唯一Id , 在当前端点中 唯一Id为Session Id
     */
    private final static Map<String, WebSocketTemplateSession> SESSION_MAP = new ConcurrentHashMap<>();
    /**
     * 通过 clientKey 反查 sessionId , key为clientKey , value 为sessionId
     */
    private final static Map<String, String>  CLIENT_KEY_SESSION_STORE_MAP = new ConcurrentHashMap<>();


    private static final Logger WEBSOCKET_TEMPLATE_PUSH_LOGGER = LoggerFactory.getLogger(WebSocketTemplateInfoPushServer.class);

    /**
     * 1. 有新的连接访问当前 websocket 地址
     *
     * @param session      当前客户端的服务器对象 session
     * @param clientCode   客户端设备唯一code码
     */
    @OnOpen
    public void doConnectionSocket(Session session, @PathParam("clientKey") String clientCode) {

        // 前端异常 通过抓包发送 则直接关闭当前创建的session对象
        if (StringUtils.isEmpty(clientCode)) {
            try {
                session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "参数不合法,已关闭当前连接!"));
            } catch (IOException e) {
                WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());
            }
        }
        // 正常则建立连接 存储数据 并返回连接成功
        else {
            String sessionId = session.getId();

            SESSION_MAP.put(sessionId, new WebSocketTemplateSession(session,clientCode));
            CLIENT_KEY_SESSION_STORE_MAP.put(sessionId,clientCode);

            ONLINE_ADDER.increment();
            WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-连接成功,此刻连接设备码为: [{}] , 此刻在线的连接数为:[{}]", clientCode, ONLINE_ADDER.sum());
        }
    }

    /**
     * 2. 关闭当前websocket连接
     * @param session
     */
    @OnClose
    public void doCloseSocket(Session session) {

        try {
            String sessionId = session.getId();
            WebSocketTemplateSession doCloseSession = SESSION_MAP.get(sessionId);
            doCloseSession.getSession().close();

            // 清除当前关联的Session信息
            SESSION_MAP.remove(sessionId);
            CLIENT_KEY_SESSION_STORE_MAP.remove(sessionId);

            ONLINE_ADDER.decrement();
            WEBSOCKET_TEMPLATE_PUSH_LOGGER.info("WebSocket-连接关闭,此刻在线的连接数为:[{}]", ONLINE_ADDER.sum());
        } catch (IOException e) {
            WEBSOCKET_TEMPLATE_PUSH_LOGGER.error(e.getMessage());
        }
    }

    /**
     * 3. 接收客户端主动发送的消息数据
     * @param session       当前会话
     * @param jsonMessage   客户端发送的JSON数据
     */
    @OnMessage
    public void receiveMessage(Session session , String jsonMessage){

        try {
        	// 收到前端发送的信息
        } catch (JSONException jsonException){
            WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("JSON格式有误,异常信息->[{}]" , jsonException.getMessage());
        } catch (Exception e){
            WEBSOCKET_TEMPLATE_PUSH_LOGGER.error("接收信息接口失败,异常信息->[{}]" , e.getMessage());
        }
    }
}

  • WebSocketTemplateSession
@Data
@AllArgsConstructor
public class WebSocketTemplateSession {

    private Session session;

    private String clientKey;

}

4. 测试使用

  • 本地的 ws 方式

  • 服务器的 wss 方式

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明
根据你提供的引用内容,问题中的错误提示是WebSocket连接失败。根据引用\[1\]中的描述,这个问题可能是由于vue-cli-service在服务器上无法正确检测到公网IP,而是使用了局域网IP导致的。引用\[2\]和引用\[3\]提供了两种修改vue.config.js中devServer配置的方法来解决这个问题。 根据引用\[2\]中的配置,可以尝试在devServer中添加以下配置: ``` client: { webSocketURL: 'ws://0.0.0.0:8999/ws' } ``` 这样可以指定WebSocket的URL为本地的IP地址和端口号。 另外,根据引用\[3\]中的配置,也可以尝试修改devServer中的配置为: ``` devServer: { host: '0.0.0.0', port: 3000, client: { webSocketURL: 'ws://0.0.0.0:3000/ws', }, headers: { 'Access-Control-Allow-Origin': '*', } } ``` 这样可以指定WebSocket的URL为本地的IP地址和端口号,并添加了跨域请求的配置。 请根据你的具体情况选择其中一种方法进行尝试,以解决WebSocket连接失败的问题。 #### 引用[.reference_title] - *1* [解决vue问题WebSocketClient.js?5586:16 WebSocket connection to ‘ws://x.x.x.60:8081/ws‘ failed](https://blog.csdn.net/youaregoo/article/details/123358613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [解决WebSocketClient.js?5586:16 WebSocket connection to ‘ws://192.168.1.102:8999/ws‘ failed:](https://blog.csdn.net/m0_62323730/article/details/128846402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [vue 报错:WebSocket connection to ‘ws://192.168.51.116:3000/ws‘ failed:](https://blog.csdn.net/sherlyxoxo/article/details/125647887)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值