"user" 来自 convertAndSendToUser 的地方在 SockJS+Spring Websocket 中工作?
Where "user" comes from in convertAndSendToUser works in SockJS+Spring Websocket?
我想了解 convertAndSendToUser 在 Spring SockJS+Websocket 框架中的工作原理。
在客户端中,我们将连接为
stompClient.connect(login, password, callback())
这将导致使用 "Stomp credentials" 的登录名和密码的连接请求,例如可以看到如果我们处理 SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
但我仍然不清楚这是否是 "user" 在服务器端向队列发送操作的意思:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
我最接近的是阅读这个线程 Sending message to specific user on Spring Websocket,由 Thanh Nguyen Van 回答,但仍然不清楚。
基本上我需要做的是为一些客户端订阅相同的主题,但在服务器上向它们发送不同的数据。客户可以提供用户标识符。
我们知道我们可以使用他订阅的主题前缀从 stomp 服务器向客户端发送消息,例如/topic/hello
。我们还知道我们可以向特定用户发送消息,因为 spring 提供了 convertAndSendToUser(username, destination, message)
API。它接受一个字符串用户名,这意味着如果我们以某种方式为每个连接拥有一个唯一的用户名,我们应该能够向订阅主题的特定用户发送消息。
不太明白的是,这个用户名是从哪里来的?
这个用户名是 java.security.Principal
接口的一部分。 每个 StompHeaderAccessor
或 WebSocketSession
对象都有这个主体的实例,你可以得到它的用户名。但是,根据我的实验,它不是自动生成的。 它必须由服务器为每个会话手动生成。
要使用这个接口首先你需要实现它。
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
@Override
String getName() {
return name
}
}
然后您可以通过覆盖 DefaultHandshakeHandler 为每个连接生成唯一的 StompPrincipal
。您可以使用任何逻辑来生成用户名。这是一个使用 UUID 的潜在逻辑:
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())
}
}
最后,您需要配置您的 websockets 以使用您的自定义握手处理程序。
@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
对象的一部分传递该主体,您可以通过连接事件侦听器、MessageMapping 函数等访问这些对象...
来自事件侦听器:
@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
// Get Accessor
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
来自映射的消息 APIs
@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
,您将只会收到广播消息。
我没有做任何具体的配置,我可以这样做:
@MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
String username = principal.getName();
}
与 Wenneguen 类似,我只需在 MessageMapping
方法
中注入 Principal
public void processMessageFromClient(@Payload String message, Principal principal) {
principal.getName()
实现来自 org.springframework.security.authentication.UsernamePasswordAuthenticationToken
class
我想了解 convertAndSendToUser 在 Spring SockJS+Websocket 框架中的工作原理。
在客户端中,我们将连接为
stompClient.connect(login, password, callback())
这将导致使用 "Stomp credentials" 的登录名和密码的连接请求,例如可以看到如果我们处理 SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
但我仍然不清楚这是否是 "user" 在服务器端向队列发送操作的意思:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
我最接近的是阅读这个线程 Sending message to specific user on Spring Websocket,由 Thanh Nguyen Van 回答,但仍然不清楚。
基本上我需要做的是为一些客户端订阅相同的主题,但在服务器上向它们发送不同的数据。客户可以提供用户标识符。
我们知道我们可以使用他订阅的主题前缀从 stomp 服务器向客户端发送消息,例如/topic/hello
。我们还知道我们可以向特定用户发送消息,因为 spring 提供了 convertAndSendToUser(username, destination, message)
API。它接受一个字符串用户名,这意味着如果我们以某种方式为每个连接拥有一个唯一的用户名,我们应该能够向订阅主题的特定用户发送消息。
不太明白的是,这个用户名是从哪里来的?
这个用户名是 java.security.Principal
接口的一部分。 每个 StompHeaderAccessor
或 WebSocketSession
对象都有这个主体的实例,你可以得到它的用户名。但是,根据我的实验,它不是自动生成的。 它必须由服务器为每个会话手动生成。
要使用这个接口首先你需要实现它。
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
@Override
String getName() {
return name
}
}
然后您可以通过覆盖 DefaultHandshakeHandler 为每个连接生成唯一的 StompPrincipal
。您可以使用任何逻辑来生成用户名。这是一个使用 UUID 的潜在逻辑:
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())
}
}
最后,您需要配置您的 websockets 以使用您的自定义握手处理程序。
@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
对象的一部分传递该主体,您可以通过连接事件侦听器、MessageMapping 函数等访问这些对象...
来自事件侦听器:
@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
// Get Accessor
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
来自映射的消息 APIs
@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
,您将只会收到广播消息。
我没有做任何具体的配置,我可以这样做:
@MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
String username = principal.getName();
}
与 Wenneguen 类似,我只需在 MessageMapping
方法
Principal
public void processMessageFromClient(@Payload String message, Principal principal) {
principal.getName()
实现来自 org.springframework.security.authentication.UsernamePasswordAuthenticationToken
class