春季启动从 1.2.0 升级到 2.1.3 后,Websocket 推送通知不起作用

Posted

技术标签:

【中文标题】春季启动从 1.2.0 升级到 2.1.3 后,Websocket 推送通知不起作用【英文标题】:Websocket Push Notifications not working AFTER spring boot upgrade from 1.2.0 to 2.1.3 【发布时间】:2019-07-29 17:02:35 【问题描述】:

这是关于使用 websocket + stompjs 发送推送通知的 spring boot + angularjs web 应用程序。

我最近从 Spring boot 1.2.0 升级到 2.1.3之前,此升级 websocket(推送通知)工作了好几年

我刚刚升级了 spring boot 和 websocket 相关的代码完全相同,但它现在工作了。

不工作意味着:

    在服务器端执行以下行没有任何错误/异常。

simpMessagingTemplate.convertAndSend("/topic/notify", payload);

    Chrome 调试器只接收“h”(心跳)而不是实际消息。

不知道因为,

服务器端代码成功执行直到最后一行。 websocket 会话建立,我可以收到心跳消息,但客户端也没有错误。

代码(但同样的代码适用于 Spring boot 1.2.0:

1.配置:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 

    @Value("$server.sessionTimeout")
    long sessionTimeoutInSecs;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) 
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) 
        registry.addEndpoint("/notify").withSockJS();
    

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() 
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // in milliseconds
        container.setMaxSessionIdleTimeout(sessionTimeoutInSecs * 1000);
        return container;
    

2。消息发送代码:

simpMessagingTemplate.convertAndSend("/topic/notify", payload);

3.客户端代码:

(function() 
  myApp.factory('autoUpdateTasksService', function($resource, $q, $log) 
    var initSockets, notify, reconnect, socket, _callback;
    _callback = null;
    socket = 
      client: null,
      stomp: null
    ;
    initSockets = function() 
      socket.client = new SockJS('/notify');
      socket.stomp = Stomp.over(socket.client);
      socket.stomp.connect(, function() );
      socket.client.onopen = function() 
        var subscription1;
        subscription1 = socket.stomp.subscribe("/topic/notify", notify);
        //$log.log('socket connected');
      ;
    ;
    reconnect = function() 
      setTimeout(initSockets, 1000);
    ;
    notify = function(message) 
         try
              var taskNotifyObject;
                  if (message.body) 
                    taskNotifyObject = angular.fromJson(message.body);
                    //$log.log(taskNotifyObject);
                    var notificationArray=[];
                    notificationArray.push(taskNotifyObject);
                    _callback(notificationArray);
                   else 
                    //$log.log("empty message");
                  
              catch(e)
                // alert(e.message); 
                 
                ;
            return 
              init: function(callback) 
                _callback = callback;
                initSockets();
              
            ;
  );

).call(this);

版本之间的spring框架有什么变化吗?

如何调试/查找消息丢失的位置?

【问题讨论】:

【参考方案1】:

根本原因:升级后,我的问题中的代码无法在服务器和客户端之间创建连接(未能创建websocketSession) .

更改代码如下解决问题,但我确定为什么这个解决方案有效,

如果有人解释为什么这个解决方案是有效的,那将是一个很大的帮助。

1.配置:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 

    @Value("$server.servlet.session.timeout")
    long sessionTimeoutInSecs;

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) 
        config.enableSimpleBroker("/queue");
        config.setApplicationDestinationPrefixes("/app");
    

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) 
        registry.addEndpoint("/notify").addInterceptors(new HttpSessionHandshakeInterceptor());
    

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() 
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // in milliseconds
        container.setMaxSessionIdleTimeout(sessionTimeoutInSecs * 1000);
        return container;
    

    /**
     * DefaultSimpUserRegistry is the replacement of MySessionRegistry ( Custom UserSessionRegistry ) after upgrade to Spring 5.
     * Below required with Spring 4.
     * import org.springframework.messaging.simp.user.UserSessionRegistry;
        @Repository
        public class MySessionRegistry implements UserSessionRegistry, ApplicationListener<AbstractSubProtocolEvent> 
     * 
     */ 
    @Bean
    public DefaultSimpUserRegistry defaultSimpUserRegistry() 
        DefaultSimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
        return userRegistry;
    

2。消息发送代码:

import org.springframework.web.socket.messaging.DefaultSimpUserRegistry;

@Autowired
DefaultSimpUserRegistry defaultSimpUserRegistry;

.....

SimpUser simpUser = defaultSimpUserRegistry.getUser(payload.getUserName());

if(simpUser != null && simpUser.hasSessions()) 
     template.convertAndSendToUser(payload.getUserName(), "/queue/notify", payload);

3.客户端代码:

(function() 
  myApp.factory('autoUpdateTasksService', function($resource, $q, $log) 
    var initSockets, notify, reconnect, socket, _callback;
    _callback = null;
    socket = 
      client: null,
      stomp: null
    ;
    getContextPath = function() 
        return window.location.pathname.substring(0, window.location.pathname.indexOf("/",2));
    ;
    initSockets = function() 
        //socket.addr = "wss://" + window.location.host + "/notify";
        socket.addr = ((window.location.protocol && (window.location.protocol.indexOf("https") >= 0)) ? "wss://" : "ws://") + window.location.host + getContextPath() + "/notify";
        socket.client = Stomp.client(socket.addr); //new SockJS('/notify');
        socket.client.connect(, function () 
          $log.log("Connected to websocket through " + socket.addr);
          socket.client.subscribe("/user/queue/notify", notify);
        , function (err) 
          $log.log("Error when connection to websocket " + socket.addr + ".\n" + err);
        );
    ;

如何调试/查找消息丢失的位置?

为了验证客户端-服务器连接(或创建 websocketSession),我在 listener 下方添加。

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;

@Component
public class WebSocketListener implements ApplicationListener <ApplicationEvent> 

            //WebSocket session created
            if (appEvent instanceof SessionConnectedEvent)
                StompHeaderAccessor sha = StompHeaderAccessor.wrap(((SessionConnectedEvent) appEvent).getMessage());
                logger.info("SessionConnectedEvent: STOMP WebSocket session created for the user: ", sha.getUser().getName());
            

            //subscribed to websocketSession
            if (appEvent instanceof SessionSubscribeEvent)
                StompHeaderAccessor sha = StompHeaderAccessor.wrap(((SessionSubscribeEvent) appEvent).getMessage());                
                logger.info("SessionSubscribeEvent: User  subscribed to WebSocket session, destination: ", sha.getUser().getName(), sha.getDestination());
            
//            
//            if (appEvent instanceof BrokerAvailabilityEvent)
//                logger.info("BrokerAvailabilityEvent: ", appEvent.toString());
//            
        

【讨论】:

以上是关于春季启动从 1.2.0 升级到 2.1.3 后,Websocket 推送通知不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何仅将 JSON 请求正文的几个字段从 DTO 发布到 URL 春季启动

SpringBoot 升级到 2.1 后,启动程序时控制台不打印 API 的解决方法及一些感想

春季启动中的@value没有从application.properties中提供价值

Spring Boot - 从 2.2.5 升级到 2.5.7 后,应用程序无法启动

升级后可能遇到的故障- -从Windows 2012升级到2016案例之3

在春季启动一段时间后,数据库连接丢失