“user”在convertAndSendToUser中来自哪里,在SockJS + Spring Websocket中工作?

2022-09-01 03:03:59

我想了解convertAndSendToUser如何在Spring SockJS + Websocket框架中工作。

在客户端中,我们将连接为

stompClient.connect(login, password, callback())

这将导致使用登录名和密码的“Stomp凭据”进行连接请求,例如,如果我们处理SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/

但是我仍然不清楚这是否是服务器端发送到队列操作中的“用户”:

 simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);

我能得到的最接近的是阅读这个线程 在Spring Websocket上向特定用户发送消息,由Thanh Nguyen Van回答,但仍然不清楚。

基本上,我需要做的是为一些客户端订阅相同的主题,但在服务器上,向他们发送不同的数据。客户端可以提供用户标识符。


答案 1

我们知道我们可以使用他订阅的主题前缀从踩踏服务器向客户端发送消息,例如。我们也知道我们可以向特定用户发送消息,因为spring提供了API。它接受一个字符串用户名,这意味着如果我们以某种方式为每个连接都有一个唯一的用户名,我们应该能够向订阅主题的特定用户发送消息。/topic/helloconvertAndSendToUser(username, destination, message)

不太了解的是,这个用户名来自哪里?

此用户名是 java.security.Principal 接口的一部分。每个 或 对象都有此主体的实例,您可以从中获取用户名。但是,根据我的实验,它不是自动生成的。它必须由服务器为每个会话手动生成。StompHeaderAccessorWebSocketSession

要首先使用此接口,您需要实现它。

class StompPrincipal implements Principal {
    String name

    StompPrincipal(String name) {
        this.name = name
    }

    @Override
    String getName() {
        return name
    }
}

然后,您可以通过覆盖 DefaultHandshakeHandler 为每个连接生成唯一连接。您可以使用任何逻辑来生成用户名。下面是一个使用 UUID 的潜在逻辑:StompPrincipal

class CustomHandshakeHandler extends DefaultHandshakeHandler {
    // Custom class for storing principal
    @Override
    protected Principal determineUser(
        ServerHttpRequest request,
        WebSocketHandler wsHandler,
        Map<String, Object> attributes
    ) {
        // Generate principal with UUID as name
        return new StompPrincipal(UUID.randomUUID().toString())
    }
}

最后,您需要将 websocket 配置为使用自定义握手处理程序。

@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    stompEndpointRegistry
         .addEndpoint("/stomp") // Set websocket endpoint to connect to
         .setHandshakeHandler(new CustomHandshakeHandler()) // Set custom handshake handler
         .withSockJS() // Add Sock JS support
}

就是这样。现在,您的服务器已配置为为每个连接生成唯一的主体名称。它将传递该主体作为对象的一部分,您可以通过连接事件侦听器,消息映射函数等访问...StompHeaderAccessor

来自事件侦听器:

@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
    // Get Accessor
    StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}

从消息映射的 API

@MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) {
    // sha available in params
}

关于使用的最后一点注意事项。向用户发送消息时,您将使用类似如下的内容convertAndSendToUser(...)

convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)

但是,对于订阅客户端,您将使用

client.subscribe('/user/topic/hello', callback)

如果订阅客户端,则只会收到广播消息。/topic/hello


答案 2

我没有做任何特定的配置,我可以这样做:

@MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
    String username = principal.getName();
}

推荐