微服务使用SockJs+Stomp实现Websocket 前后端实例 | Vuex形式断开重连、跨域等等问题踩坑(一)

news2025/1/16 12:36:51

大家好,我是程序员大猩猩。

之前几篇文章,我们讲了Spring Cloud Gateway的轻量级实现,Nginx的配置概念与实现,如以下往期文章。

轻量级的Spring Cloud Gateway实践,实现api和websocket转发
轻松实现Nginx的HTTP与WebSocket转发:你的网站需要这个!

以上我们提到了SockJs和Stomp,对于Gateway与SockJs的转发连接友好性,那么我们今天就来通过实践来完成这些实例。

首先,我们来了解一下SokeJs和Stomp。

什么是 SockJS

SockJS 是一种浏览器与服务器之间的通信协议,它可以在浏览器和服务器之间建立一个基于 HTTP 的双向通信通道。SockJS 的主要作用是提供一种 WebSocket 的兼容性解决方案,使得不支持 WebSocket 的浏览器也可以使用 WebSocket。

当浏览器不支持 WebSocket 时,SockJS 会自动切换到使用轮询(polling)或长轮询(long-polling)的方式进行通信。

在使用 SockJS 时,首先需要在客户端和服务器端分别引入 sockjs-client.js 和 sockjs-server,然后在客户端通过 new SockJS(url) 的方式建立一个 SockJS 连接。

客户端和服务器端之间的通信是基于事件的,当客户端发送消息时,服务器端会触发一个 onmessage 事件,然后将消息发送回客户端。客户端在接收到消息后,会触发一个 onmessage 事件,然后处理收到的消息。

我们可以在前端代码中使用以下语句来实例化它:

new SockJS('http://*****:8080/ws/user'); // 连接后端接口

什么是 Stomp

STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。

同样,我们怎么初始化使用它:

var url = "ws://*****:8080/ws/user";
var client = Stomp.client(url);
后端实现

当我们工程项目创建好之后,pom内直接引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
首先,我们来完成配置类:
@Configuration
// 注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebScoketConfig implements WebSocketMessageBrokerConfigurer {

    // 输入通道拦截器
    @Resource
    private InboundChannelInterceptor inboundChannelInterceptor;

    // 请求头认证信息使用
    @Resource
    private PrincipalHandshakeHandler principalHandshakeHandler;

    /**
     * <b>功能描述:</b>注册STOMP协议的节点(endpoint),并映射指定的url<br>
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //注册一个STOMP的endpoint,并指定使用SockJS协议
        registry.addEndpoint("/ws")
                .setHandshakeHandler(principalHandshakeHandler)
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }


    /**
     * <b>功能描述:</b>配置消息代理(Message Broker)<br>
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理,群发(mass),单独聊天(queue)
        //推送消息前缀
        registry.enableSimpleBroker("/topic");
        //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
        // 应用请求前缀
        // 推送用户前缀
        registry.setUserDestinationPrefix("/user");
    }
    
     /**
     * <b>功能描述:</b>输入通道配置<br>
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(this.inboundChannelInterceptor);// 设置拦截器
        registration.taskExecutor()    // 线程信息
                .corePoolSize(10)     // 核心线程池
                .maxPoolSize(20)      // 最多线程池数
                .keepAliveSeconds(60); // 超过核心线程数后,空闲线程超时60秒则杀死
    }

       /**
     * <b>功能描述:</b>消息传输参数配置<br>
     */
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setSendTimeLimit(15 * 1000)    // 超时时间
                .setSendBufferSizeLimit(512 * 1024) // 缓存空间
                .setMessageSizeLimit(128 * 1024);   // 消息大小
    }
}

@EnableWebSocketMessageBroker表示启用Socket代理。

registerStompEndpoints方法内addEndpoint表示接口前缀,当前端连接时,使用http://****:8080/ws方式接入。

setHandshakeHandler添加认证请求头的认证类。

setAllowedOriginPatterns跨域处理

withSockJS是注册SockJS代理

拦截器实现

@Slf4j
@Component
public class InboundChannelInterceptor implements ChannelInterceptor {

    // 后端实现
    @Resource
    private IWebSocketService webSocketServiceImpl;

    @SneakyThrows
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (accessor == null) {
            log.error("accessor is null");
            return message;
        }
        StompCommand stompCommand = accessor.getCommand();
        String simpSessionId = accessor.getHeader("simpSessionId").toString();
        String userId = accessor.getFirstNativeHeader("userId");
        if (StompCommand.CONNECT.equals(stompCommand)) {
            this.webSocketServiceImpl.connect(simpSessionId, userId);
        } else if (StompCommand.DISCONNECT.equals(stompCommand)) {
            this.webSocketServiceImpl.disconnect(simpSessionId);
        } else if (StompCommand.SEND.equals(stompCommand)) {
            this.webSocketServiceImpl.ping(simpSessionId, userId);
        }
        return message;
    }
}

认证信息类

@Slf4j
@Component
public class PrincipalHandshakeHandler extends DefaultHandshakeHandler {

    /**
     * <b>功能描述:</b>请求头<br>
     */
    public static final String ACCESS_TOKEN = "token";


    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
        /**
         * 这边可以按需求,如何获取唯一的值,既unicode
         * 得到的值,会在监听处理连接的属性中,既WebSocketSession.getPrincipal().getName()
         * 也可以自己实现Principal()
         */
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
            HttpServletRequest httpRequest = servletServerHttpRequest.getServletRequest();
            /**
             * 携带参数,你可以cookie,请求头,或者url携带,这边我采用url携带
             */
            String header = httpRequest.getHeader(ACCESS_TOKEN);
            log.info("token:{}", header);
            final String token = httpRequest.getParameter(ACCESS_TOKEN);
            if (StrUtil.isEmpty(token)) {
                return null;
            }
            return () -> token;
        }
        return null;
    }
}

​​​​​​​我们使用Dug模式启动服务看看是否完成,并看看它的Mappings列表。

图片

我们本地输入链接查看,部署成功。

图片

踩坑问题:

1.setAllowedOriginPatterns跨域请求只是一个小点,因为SockJs会封装一个sock-node/info?t=...的接口,我们还必须要全局的设置跨域。

另外网络其他博客,很多会说这个接口404的问题,然后注释掉socket-client什么node_modules js内的1600的行代码。

我是真不信,最后我把我后端代码跨域处理后,就可用了,后端这个接口是默认开放的。有些东西我们真的不要信。

@Component
public class SimpleCORSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD,PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With, token");
        HttpServletRequest request = (HttpServletRequest) req;
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

​​​​​​​

图片

成功,下节我们来看看前端Vue的实现,再见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1608788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenHarmony实战开发-搜索功能实现案例、如何使用includes方法对数据实现模糊查询

介绍 本示例介绍使用includes方法对数据实现模糊查询 效果图预览 使用说明 点击首页搜索框跳转到搜索页面在搜索页面输入框中输入搜索的内容&#xff0c;下方列表自动根据搜索的内容进行筛选渲染点击筛选后的列表跳转到相应的页面跳转后会保存搜索历史&#xff0c;搜索历史使…

14-Error Handling (错误处理)

ESP32-S3错误处理&#xff1a;理解并掌握其重要性 在编程中&#xff0c;错误处理是一种重要的编程实践&#xff0c;它可以帮助我们的程序在遇到错误时能够优雅地恢复&#xff0c;而不是崩溃或产生不可预测的结果。在IDF中官方提供一些实用的错误处理技巧。&#x1f469;‍&…

java扩展jmeter依赖

前置条件 创建一个maven项目&#xff0c; 引入依赖 <dependency><groupId>org.apache.jmeter</groupId><artifactId>ApacheJMeter_core</artifactId><version>3.2</version> </dependency> <dependency><groupId&g…

吴恩达2022机器学习专项课程(一) 6.1 动机第三周课后实验:Lab1使用逻辑回归进行分类

问题预览/关键词 回归和分类的区别&#xff1f;逻辑回归的作用是&#xff1f;什么是二分类问题&#xff1f;二分类问题案例如何表达二分类的结果&#xff1f;逻辑回归通常用哪种表达形式&#xff1f;什么是正样本和负样本&#xff1f;什么是阈值&#xff1f;可视化线性回归解决…

论婚恋相亲交友软件的市场前景和开发方案H5小程序APP源码

随着移动互联网的快速发展和社交需求的日益增长&#xff0c;婚恋相亲交友软件小程序成为了越来越多单身人士的选择。本文将从市场前景、使用人群、盈利模式以及竞品分析等多个角度&#xff0c;综合论述这一领域的现状与发展趋势。 一、市场前景 在快节奏的现代生活中&#xf…

ubuntu 22.04 编译 ORBSLAM3

源码地址&#xff08;带注释&#xff09;&#xff1a;ORBSLAM3 根据你安装的openCv版本修改cmake 修改2个文件的2个地方&#xff1a; ORB_SLAM3_detailed_comments-master/CMakeLists.txt ORB_SLAM3_detailed_comments-master/Thirdparty/DBoW2/CMakeLists.txt 查找openCv的地…

Python 爬虫如何配置代理 IP (Py 采集)

在Python中配置代理IP&#xff0c;可以通过设置requests库的proxies参数来实现。以下是一个示例&#xff1a; import requests# 则立可以获取稳定代理Ip&#xff1a;https://www.kuaidaili.com/?refrg3jlsko0ymg # 推荐使用私密动态 IP proxies {"http": "ht…

Mini-Gemini: Mining the Potential of Multi-modality Vision Language Models论文解读

文章目录 前言一、摘要二、引言三、文献1、大语言模型文献2、视觉语言模型文献3、LLM作为生成助手文献 四、模型方法与结果1、Dual Vision Encoders3、Patch Info Mining4、Text and Image Generation1、Text-image Instructions2、Generation-related Instructions 五、实验结…

webAssembly学习及使用rust

学习理解 webAssembly 概念知识&#xff0c;使用 API 进行 web 前端开发。 概念 是一种运行在现代网络浏览器中的新型代码&#xff0c;并且提供新的性能特性和效果。它有一种紧凑的二进制格式&#xff0c;使其能够以接近原生性能的速度运行。C/C、 C#、Rust等语言可以编译为 …

手写Java设计模式之抽象工厂模式,附源码解读

接上篇&#xff0c;抽象工厂模式将汽车的一些属性可以抽象出来&#xff0c;可以理解为给不同汽车品牌生成时加上不同的特性&#xff0c;如颜色等&#xff0c;具体代码如下&#xff1a; 引入颜色接口&#xff1a; public interface Colour {void fill(); }将颜色与汽车生成品牌…

Django项目使用uwsgi+nginx部署上线

Django项目使用uwsginginx部署上线 前言settings 配置安装uwsgi 和配置uwsgi推荐配置文件启用wsgi不使用nginx的配置&#xff08;不推荐&#xff09;使用nginx的配置 安装 nginx和配置niginx 配置 运行参考资料 前言 代码已经开发完成&#xff0c;正式部署上线 settings 配置…

公网IP地址如何申请SSL证书?有免费的IP ssl吗?

如果用户没有域名或只有公网IP地址或者不方便使用域名&#xff0c;IP地址ssl证书这一特殊的证书可以为IP地址实现HTTPS的安全保护&#xff0c;提高网站数据传输的安全性。 IP地址申请SSL证书的基本步骤 IP ssl证书下载---注册填写230916https://www.joyssl.com/certificate/sel…

Stable Diffusion UI 从安装到实现文字图片融合(光影字,错觉图)图片制作详细教程

前言 最近在实践大模型本地部署&#xff0c;前几天在本地部署了一个ChatGLM大模型&#xff0c;刚好环境搭好了&#xff0c;也支持跑Stable Diffusion&#xff0c;所以就安装了再尝试一下。 原因是之前在B站上有大佬做了一个Windows电脑能一键运行的Stable Diffusion的安装包&…

镜舟科技荣获金科创新社 2024 年度金融数据智能解决方案奖

近日&#xff0c; 镜舟科技凭借领先的金融实时数仓构建智能经营解决方案&#xff0c;在“金科创新社第六届金融数据智能优秀解决方案评选”活动中&#xff0c;成功入选“数据治理与数据平台创新优秀解决方案”榜单。 金科创新社主办的“鑫智奖”评选活动&#xff0c;旨在展示…

密码学 | 承诺:基本概念

目录 正文 1 承诺的交互 2 承诺的属性 3 硬币抛掷问题 3.1 朴素版方案 3.2 承诺版方案 &#x1f951;源自&#xff1a;https://en.wikipedia.org/wiki/Commitment_scheme &#x1f951;写在前面&#xff1a;英文的承诺是 commitment scheme&#xff0c;否则很难进行…

Unity射击游戏开发教程:(1)玩家控制

玩家的移动 玩家控制和移动是视频游戏中最酷的事情之一,因为你正在控制游戏中的某些东西 现在游戏中的玩家是我们的蓝色方块英雄。我在游戏开发中了解到,游戏是用简单的对象制作原型,然后添加所有漂亮的艺术和声音。代码… 我们要做的第一件事是在游戏开始时为玩家提供一个…

uniapp H5项目 获取接口的二进制流转化成图片url(base64)

如果你使用的是uniapp, 并且你从接口获取下来的数据长这样&#xff1a; 想要把取到的数据展示成图片&#xff0c;那么你可以这样做&#xff1a; // 这是我们的项目封装的请求方法const res await this.$api.getKaptcha({originResponse: true, // 这样写是为了在request那边特…

js中let和var的区别

在JavaScript中&#xff0c;var、let和const都用于声明变量&#xff0c;但它们之间存在一些重要的区别。特别是let和var之间的区别&#xff0c;我们可以概括为以下几点&#xff1a; 作用域&#xff08;Scope&#xff09;&#xff1a;var有函数作用域或全局作用域&#xff0c;而…

STM32实现硬件I2C通讯,读取MPU6050的ID号

今天学习了使用硬件I2C的方式成功读取MPU6050的ID号&#xff0c;特此记录一下过程&#xff1a; 首先需要学习的是MPU6050的初始化&#xff1a; 第一步&#xff1a;打开GPIOB的时钟&#xff08;因为I2C2的引脚10,11在GPIOB上&#xff09; 第二步&#xff1a;打开I2C2的时钟 …

09 MySQL--操作真题

1. 用一条 SQL 语句&#xff0c;查询出每门课程都大于 80 分的人。 分析&#xff1a; 去重查询出存在课程小于 80 分的人&#xff0c;设为集合A查询不在集合 A 中的人 # 第一步&#xff1a;找小于等于80分的学员姓名 select distinct name from t_student where fenshu <…