在没有 spring 身份验证的情况下通过网络套接字回复多次

Replying multiple times over web-socket without spring authentication

注意:请参阅问题底部的更新以了解我最终得出的结论。

我需要通过发送请求消息的网络套接字向请求发送多个响应,第一个响应很快,其他响应在数据验证后(10 到 60 秒后,来自多个并行线程) ).

我在获取停止通过所有打开的网络套接字进行广播的后续响应时遇到了问题。如何让它们只发送到初始网络套接字?或者我应该使用 Spring STOMP 之外的东西(因为,​​老实说,我想要的只是消息路由到各种功能,我不需要或不想要广播到其他网络套接字的能力,所以我怀疑我可以自己写消息分发器,虽然是在重新发明轮子)。

我没有使用 Spring 身份验证(这正在改进到遗留代码中)。

在最初的 return 消息中,我可以使用 @SendToUser,即使我们没有用户,Spring 也只会将 return 值发送到 websocket发送消息。 (参见 this question)。

虽然响应速度较慢,但​​我想我需要使用 SimpMessagingTemplate.convertAndSendToUser(user, destination, message),但我不能,因为我必须传入用户,我不能'不知道@SendToUser 使用的是哪个用户。我尝试按照 的步骤进行操作,但在未通过身份验证时无法正常工作(principal.getName() return 在这种情况下为 null)。

我已经为原型大大简化了这一点,所以不用担心同步线程或任何事情。我只是想让网络套接字正常工作。

这是我的控制器:

@Controller
public class TestSocketController
{
  private SimpMessagingTemplate template;

  @Autowired
  public TestSocketController(SimpMessagingTemplate template)
  {
    this.template = template;
  }

  // This doesn't work because I need to pass something for the first parameter.
  // If I just use convertAndSend, it broacasts the response to all browsers
  void setResults(String ret)
  {
    template.convertAndSendToUser("", "/user/topic/testwsresponse", ret);
  }

  // this only sends "Testing Return" to the browser tab hooked to this websocket
  @MessageMapping(value="/testws")
  @SendToUser("/topic/testwsresponse")
  public String handleTestWS(String msg) throws InterruptedException
  {
    (new Thread(new Later(this))).start();
    return "Testing Return";
  }

  public class Later implements Runnable
  {
    TestSocketController Controller;
    public Later(TestSocketController controller)
    {
        Controller = controller;
    }

    public void run()
    {
        try
        {
            java.lang.Thread.sleep(2000);

            Controller.setResults("Testing Later Return");
        }
        catch (Exception e)
        {
        }
    }
  }
}

为了记录,这里是浏览器端:

var client = null;
function sendMessage()
{
    client.send('/app/testws', {}, 'Test');
}

// hooked to a button
function test()
{
    if (client != null)
    {
        sendMessage();
        return;
    }

    var socket = new SockJS('/application-name/sendws/');
    client = Stomp.over(socket);
    client.connect({}, function(frame)
    {
        client.subscribe('/user/topic/testwsresponse', function(message)
        {
            alert(message);
        });

        sendMessage();
    });
});

这是配置:

@Configuration
@EnableWebSocketMessageBroker
public class TestSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config)
    {
        config.setApplicationDestinationPrefixes("/app");
        config.enableSimpleBroker("/queue", "/topic");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry)
    {
        registry.addEndpoint("/sendws").withSockJS();
    }
}

更新:由于信息通过其他网络套接字而不是原始套接字发送的可能性涉及安全问题,我最终向我的团队推荐我们不使用 STOMP 的 Spring 4.0 实现通过网络套接字。我理解为什么 Spring 团队会按照他们的方式来做,而且它比我们需要的更强大,但是我们项目的安全限制足够严格,实际需求也足够简单,所以我们决定走不同的路。这不会使下面的答案无效,因此请根据您的项目需要做出自己的决定。至少我们希望所有人都了解了技术的局限性,无论好坏。

这不是完整的答案。只是一般性的考虑和建议。 你不能通过同一个套接字做不同的事情或连接类型。为什么不为不同的工作使用不同的插座?有些有身份验证,有些没有。有些用于快速任务,有些用于长期执行。

为什么不为每个客户使用单独的主题?

  1. 客户端生成会话 ID。

    var sessionId = Math.random().toString(36).substring(7);

  2. 客户端订阅/topic/testwsresponse/{sessionId},然后向'/app/testws/{sessionId}'发送消息。

  3. 在您的控制器中,您使用@MessageMapping(value="/testws/{sessionId}") 并移除@SendToUser。您可以使用 @DestinationVariable 在您的方法中访问 sessionId。
  4. 控制器向 /topic/testwsresponse/{sessionId} 发送进一步的响应。

本质上,当您使用用户目标时,Spring 会在内部做类似的事情。由于您不使用 Spring 身份验证,因此您不能依赖此机制,但您可以按照我上面的描述轻松实现自己的机制。

var client = null;
var sessionId = Math.random().toString(36).substring(7);
function sendMessage()
{
    client.send('/app/testws/' + sessionId, {}, 'Test');
}

// hooked to a button
function test()
{
    if (client != null)
    {
        sendMessage();
        return;
    }

    var socket = new SockJS('/application-name/sendws/');
    client = Stomp.over(socket);
    client.connect({}, function(frame)
    {
        client.subscribe('/topic/testwsresponse/' + sessionId, function(message)
        {
            alert(message);
        });

        // Need to wait until subscription is complete
        setTimeout(sendMessage, 1000);
    });
}); 

控制器:

@Controller
public class TestSocketController
{
    private SimpMessagingTemplate template;

    @Autowired
    public TestSocketController(SimpMessagingTemplate template)
    {
        this.template = template;
    }

    void setResults(String ret, String sessionId)
    {
        template.convertAndSend("/topic/testwsresponse/" + sessionId, ret);
    }

    @MessageMapping(value="/testws/{sessionId}")
    public void handleTestWS(@DestinationVariable String sessionId, @Payload String msg) throws InterruptedException
    {
        (new Thread(new Later(this, sessionId))).start();
        setResults("Testing Return", sessionId);
    }

    public class Later implements Runnable
    {
        TestSocketController Controller;
        String sessionId;
        public Later(TestSocketController controller, String sessionId)
        {
            Controller = controller;
            this.sessionId = sessionId;
        }

        public void run()
        {
            try
            {
                java.lang.Thread.sleep(2000);

                Controller.setResults("Testing Later Return", sessionId);
            }
            catch (Exception e)
            {
            }
        }
    }
}

刚刚测试过,按预期工作。