通过网络套接字多次回复,无需弹簧身份验证

2022-09-03 15:48:01

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

我需要通过 Web 套接字向发送请求消息的请求发送多个响应,第一个响应快速发送,其他响应在数据验证后(大约 10 到 60 秒后,从多个并行线程发送)。

我无法获得以后的响应以停止在所有打开的Web套接字上广播。如何让它们仅发送到初始 Web 套接字?或者我应该使用Spring STOMP之外的东西(因为,说实话,我想要的只是将消息路由到各种功能,我不需要或想要广播到其他Web套接字的能力,所以我怀疑我可以自己编写消息分发器,即使它正在重新发明轮子)。

我没有使用Spring Authentication(这被改造成遗留代码)。

在初始返回消息上,我可以使用@SendToUser,即使我们没有用户,Spring也只将返回值发送到发送消息的websocket。(请参阅此问题)。

但是,由于响应速度较慢,我认为我需要使用SimpMessagingTemplate.convertAndSendToUser(user,destination,message),但我不能,因为我必须传入用户,并且我无法弄清楚@SendToUser使用哪个用户。我试图按照这个问题中的步骤操作,但在未经身份验证时没有让它工作(principal.getName()在这种情况下返回null)。

我已经为原型大大简化了这一点,所以不用担心同步线程或任何东西。我只是希望Web套接字正常工作。

这是我的控制器:

@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();
    }
}

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


答案 1

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

  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)
            {
            }
        }
    }
}

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


答案 2

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


推荐