在没有Spring身份验证的情况下通过Web-socket多次回复。

15
注意:请参见问题底部的更新内容,了解我最终得出的结论。
我需要通过Web套接字向发送请求消息的客户端发送多个响应,第一个响应需要快速返回,其他响应需要在数据验证后(大约在10到60秒之间,来自多个并行线程)发送。
我遇到了一些问题,无法将后续响应仅发送到初始Web套接字。我该如何做才能让它们只发送到初始Web套接字?或者,我应该使用Spring STOMP以外的东西(因为说实话,我只想要将消息路由到各种函数,我不需要也不想要广播到其他Web套接字,所以我认为我可以编写自己的消息分发器,即使这是重新发明轮子)。
我没有使用Spring身份验证(这是针对旧代码进行的改进)。
在初始返回消息中,我可以使用@SendToUser,即使我们没有用户,Spring也只会将返回值发送到发送消息的Web套接字。(请参见this question)。
尽管响应速度较慢,我认为我需要使用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();
    }
}

更新:由于存在可能通过其他WebSocket发送信息的安全问题,我建议我的团队不要使用Spring 4.0实现的STOMP over Web Sockets。我理解Spring团队为什么这样做,它比我们需要的更强大,但是我们项目的安全限制非常严格,实际需求又非常简单,所以我们决定走另一条路。这并不否定下面的答案,因此请根据您的项目需求自行决定。至少我们希望所有人都了解技术的局限性,无论好坏。

2个回答

7
为什么不为每个客户端使用单独的主题?
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)
            {
            }
        }
    }
}

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


我对此有几个安全问题。首先,有人可能会找出别人正在使用的随机字符串并监听对话。其次,有人可以注册一些(或所有)潜在的字符串并听取所有响应。即使幸运地听到另一个人的响应也不足以保证此项目的安全性。我真的只需要通过单个Web套接字发送消息,我开始觉得Spring实现的STOMP不能做到这一点。 - Guy Schalnat
你不用担心安全问题。只需使用足够长的随机字符串或使用UUID作为会话ID即可。参考http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates ,基本上黑客平均需要170亿年才能破解您的系统。 - medvedev1088
我更有可能猜到你的Gmail密码,而黑客猜到随机会话ID的概率更小。 - medvedev1088
@GuySchalnat 感谢您接受我的答案。您采用了什么解决方案? - medvedev1088
我还没有时间回头去尝试原型,但我们的需求足够简单(一个浏览器到服务器的消息和三个服务器到浏览器的响应),可能更容易自己编写控制器。它可能只是Java类名称后跟要反序列化的JSON字符串。 - Guy Schalnat
显示剩余3条评论

0

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


因为在幕后,请求会启动单独的线程来执行持续时间较长的作业,如果我关闭第一个连接,就无法轻松地再次与这些线程进行通信。现在我所拥有的是线程将内容写入数据库,ajax调用每隔几秒钟从服务器群中拉取任何一个Web服务器,读取数据库并查看是否完成了任何任务。通过使用一个打开状态的Web套接字,我将其锁定到一个服务器上(一小段时间),并使线程能够直接响应,而无需涉及数据库。 - Guy Schalnat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接