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


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

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

    以上我们提到了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)进行交互。

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

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

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

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-websocket</artifactId>
    4. </dependency>
    首先,我们来完成配置类:
    1. @Configuration
    2. // 注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
    3. @EnableWebSocketMessageBroker
    4. public class WebScoketConfig implements WebSocketMessageBrokerConfigurer {
    5. // 输入通道拦截器
    6. @Resource
    7. private InboundChannelInterceptor inboundChannelInterceptor;
    8. // 请求头认证信息使用
    9. @Resource
    10. private PrincipalHandshakeHandler principalHandshakeHandler;
    11. /**
    12. * 功能描述:注册STOMP协议的节点(endpoint),并映射指定的url
    13. */
    14. @Override
    15. public void registerStompEndpoints(StompEndpointRegistry registry) {
    16. //注册一个STOMP的endpoint,并指定使用SockJS协议
    17. registry.addEndpoint("/ws")
    18. .setHandshakeHandler(principalHandshakeHandler)
    19. .setAllowedOriginPatterns("*")
    20. .withSockJS();
    21. }
    22. /**
    23. * 功能描述:配置消息代理(Message Broker)
    24. */
    25. @Override
    26. public void configureMessageBroker(MessageBrokerRegistry registry) {
    27. //点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理,群发(mass),单独聊天(queue)
    28. //推送消息前缀
    29. registry.enableSimpleBroker("/topic");
    30. //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
    31. // 应用请求前缀
    32. // 推送用户前缀
    33. registry.setUserDestinationPrefix("/user");
    34.     }
    35.     
    36.      /**
    37. * 功能描述:输入通道配置
    38. */
    39. @Override
    40. public void configureClientInboundChannel(ChannelRegistration registration) {
    41. registration.interceptors(this.inboundChannelInterceptor);// 设置拦截器
    42. registration.taskExecutor() // 线程信息
    43. .corePoolSize(10) // 核心线程池
    44. .maxPoolSize(20) // 最多线程池数
    45. .keepAliveSeconds(60); // 超过核心线程数后,空闲线程超时60秒则杀死
    46. }
    47. /**
    48. * 功能描述:消息传输参数配置
    49. */
    50. @Override
    51. public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
    52. registration.setSendTimeLimit(15 * 1000) // 超时时间
    53. .setSendBufferSizeLimit(512 * 1024) // 缓存空间
    54. .setMessageSizeLimit(128 * 1024); // 消息大小
    55. }
    56. }

    @EnableWebSocketMessageBroker表示启用Socket代理。

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

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

    setAllowedOriginPatterns跨域处理

    withSockJS是注册SockJS代理

    拦截器实现

    1. @Slf4j
    2. @Component
    3. public class InboundChannelInterceptor implements ChannelInterceptor {
    4.     // 后端实现
    5. @Resource
    6. private IWebSocketService webSocketServiceImpl;
    7. @SneakyThrows
    8. @Override
    9. public Message preSend(Message message, MessageChannel channel) {
    10. StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
    11. if (accessor == null) {
    12. log.error("accessor is null");
    13. return message;
    14. }
    15. StompCommand stompCommand = accessor.getCommand();
    16. String simpSessionId = accessor.getHeader("simpSessionId").toString();
    17. String userId = accessor.getFirstNativeHeader("userId");
    18. if (StompCommand.CONNECT.equals(stompCommand)) {
    19. this.webSocketServiceImpl.connect(simpSessionId, userId);
    20. } else if (StompCommand.DISCONNECT.equals(stompCommand)) {
    21. this.webSocketServiceImpl.disconnect(simpSessionId);
    22. } else if (StompCommand.SEND.equals(stompCommand)) {
    23. this.webSocketServiceImpl.ping(simpSessionId, userId);
    24. }
    25. return message;
    26. }
    27. }

    认证信息类

    1. @Slf4j
    2. @Component
    3. public class PrincipalHandshakeHandler extends DefaultHandshakeHandler {
    4. /**
    5. * 功能描述:请求头
    6. */
    7. public static final String ACCESS_TOKEN = "token";
    8. @Override
    9. protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) {
    10. /**
    11. * 这边可以按需求,如何获取唯一的值,既unicode
    12. * 得到的值,会在监听处理连接的属性中,既WebSocketSession.getPrincipal().getName()
    13. * 也可以自己实现Principal()
    14. */
    15. if (request instanceof ServletServerHttpRequest) {
    16. ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
    17. HttpServletRequest httpRequest = servletServerHttpRequest.getServletRequest();
    18. /**
    19. * 携带参数,你可以cookie,请求头,或者url携带,这边我采用url携带
    20. */
    21. String header = httpRequest.getHeader(ACCESS_TOKEN);
    22. log.info("token:{}", header);
    23. final String token = httpRequest.getParameter(ACCESS_TOKEN);
    24. if (StrUtil.isEmpty(token)) {
    25. return null;
    26. }
    27. return () -> token;
    28. }
    29. return null;
    30. }
    31. }

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

    图片

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

    图片

    踩坑问题:

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

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

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

    1. @Component
    2. public class SimpleCORSFilter implements Filter {
    3. @Override
    4. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    5. HttpServletResponse response = (HttpServletResponse) res;
    6. response.setHeader("Access-Control-Allow-Credentials", "true");
    7. response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
    8. response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD,PUT");
    9. response.setHeader("Access-Control-Max-Age", "3600");
    10. response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With, token");
    11. HttpServletRequest request = (HttpServletRequest) req;
    12. if ("OPTIONS".equals(request.getMethod())) {
    13. response.setStatus(HttpServletResponse.SC_OK);
    14. return;
    15. }
    16. chain.doFilter(req, res);
    17. }
    18. @Override
    19. public void init(FilterConfig filterConfig) {
    20. }
    21. @Override
    22. public void destroy() {
    23. }
    24. }

    ​​​​​​​

    图片

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

  • 相关阅读:
    IDEA最实用的设置
    YOLOv5训练自己的数据集(超详细)
    node.js知识系列(1)-每天了解一点
    C# 使用 LibUsbDotNet 实现 USB 设备检测
    springboot项目面试题
    计算机毕业设计ssm千益校园帮跑腿信息平台5e9ev系统+程序+源码+lw+远程部署
    链表【Linked List】
    解决WPF+Avalonia在openKylin系统下默认字体问题
    python将dataframe按需绘制折线图、柱状图、双坐标图
    几个西门子PLC常见通讯问题的解决方法
  • 原文地址:https://blog.csdn.net/t610654893/article/details/137963103