Spring WebSockets: Send Messages to a Specific User – Spring的WebSockets 向特定用户发送消息

最后修改: 2018年 9月 9日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll describe how to use Spring WebSockets to send STOMP messages to a single user. That’s important because we sometimes don’t want to broadcast every message to every user. Besides that, we’ll demonstrate how to send these messages in a secure way.

在本教程中,我们将介绍如何使用Spring WebSockets向单个用户发送STOMP消息。这很重要,因为我们有时不想向每个用户广播每条消息。除此之外,我们将演示如何以安全的方式发送这些消息。

For an introduction to WebSockets, check out this great tutorial for how to get up and running. And, for a deeper dive into security, check out this article to secure your WebSockets implementation.

关于WebSockets的介绍,请查看这个伟大的教程,了解如何启动和运行。此外,要深入了解安全性,请查看这篇文章,以确保WebSockets实现的安全性。

2. Queues, Topics, and Endpoints

2.队列、主题和端点

There are three main ways to say where messages are sent and how they are subscribed to using Spring WebSockets and STOMP:

使用Spring WebSockets和STOMP,有三种主要方式来说明消息的发送地点和订阅方式

  1. Topics – common conversations or chat topics open to any client or user
  2. Queues – reserved for specific users and their current sessions
  3. Endpoints – generic endpoints

Now, let’s take a quick look at an example context path for each:

现在,让我们快速看一下每个人的上下文路径的例子。

  • “/topic/movies”
  • “/user/queue/specific-user”
  • “/secured/chat”

It’s important to note that we must use queues to send messages to specific users, as topics and endpoints don’t support this functionality.

需要注意的是,我们必须使用队列来向特定用户发送消息,因为主题和端点不支持这种功能

3. Configuration

3.配置

Now, let’s learn how to configure our application so that we can send messages to a specific user:

现在,让我们学习如何配置我们的应用程序,以便我们可以向特定的用户发送消息。

public class SocketBrokerConfig extends 
  AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/secured/user/queue/specific-user");
        config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
        config.setUserDestinationPrefix("/secured/user");
    }

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

Let’s make sure to include a user destination since that determines which endpoints are reserved for single users.

让我们确保包括一个用户目的地,因为这决定了哪些端点被保留给单个用户。

We also prefix all of our queues and user destinations with “/secured” to make them require authentication. For unprotected endpoints, we can drop the “/secured” prefix (as a result of our other security settings).

我们还在所有队列和用户目的地前加上“/secured”,使其需要认证。对于不受保护的端点,我们可以放弃“/secured”前缀(作为我们其他安全设置的结果)。

From a pom.xml standpoint, no additional dependencies are required.

pom.xml的角度来看,不需要额外的依赖性。

4. URL Mappings

4.URL映射[/strong

We want our client to subscribe to a queue using a URL mapping that conforms to the following pattern:

我们希望我们的客户使用符合以下模式的URL映射来订阅一个队列。

"/user/queue/updates"

This mapping will be automatically transformed by UserDestinationMessageHandler into the user-session-specific address.

这个映射将由UserDestinationMessageHandler自动转化为用户会话的特定地址。

For example, if we have a user named “user123”, the corresponding address would be:

例如,如果我们有一个名为“user123”的用户,相应的地址将是。

"/queue/updates-user123"

Server-side, we’ll send our user-specific response using the following URL mapping pattern:

在服务器端,我们将使用以下URL映射模式发送我们的用户特定响应。

"/user/{username}/queue/updates"

This too will be transformed into the correct URL mapping we already subscribed to client-side.

这也将被转化为我们已经订阅的客户端的正确URL映射。

Thus, we see that the essential ingredients here are two-fold:

因此,我们看到,这里的基本成分有两个方面:

  1. Prepend our specified User Destination Prefix (configured in AbstractWebSocketMessageBrokerConfigurer).
  2. Use “/queue” somewhere within the mapping.

In the next section, we’ll take a look at exactly how to do this.

在下一节,我们将看看究竟如何做到这一点。

5. Invoking convertAndSendToUser()

5.调用convertAndSendToUser()

We can non-statically invoke convertAndSendToUser()  from SimpMessagingTemplate or SimpMessageSendingOperations:

我们可以从SimpMessagingTemplateSimpMessageSendingOperations非静态地调用convertAndSendToUser()

@Autowired
private SimpMessagingTemplate simpMessagingTemplate;

@MessageMapping("/secured/room") 
public void sendSpecific(
  @Payload Message msg, 
  Principal user, 
  @Header("simpSessionId") String sessionId) throws Exception { 
    OutputMessage out = new OutputMessage(
      msg.getFrom(), 
      msg.getText(),
      new SimpleDateFormat("HH:mm").format(new Date())); 
    simpMessagingTemplate.convertAndSendToUser(
      msg.getTo(), "/secured/user/queue/specific-user", out); 
}

You might have noticed:

你可能已经注意到了。

@Header("simpSessionId") String sessionId

The @Header annotation allows access to headers exposed by the inbound message. For example, we can grab the current sessionId without the need for complicated interceptors. Similarly, we can access the current user via Principal.

@Header注解允许访问入站消息暴露的头信息。例如,我们可以抓取当前的sessionId而不需要复杂的拦截器。同样,我们可以通过Principal访问当前用户。

Importantly, the approach we take in this article provides greater customization over the @sendToUser annotation with respect to URL mappings. For more on that annotation, check out this great article.

重要的是,我们在本文中所采用的方法在 URL 映射方面为 @sendToUser 注解提供了更大的自定义功能。有关该注解的更多信息,请查看这篇优秀文章。

Client-side, we’ll use connect() in JavaScript to initialize a SockJS instance and connect to our WebSocket server using STOMP:

在客户端,我们将使用JavaScript中的connect()初始化一个SockJS实例,并使用STOMP连接到我们的WebSocket服务器:

var socket = new SockJS('/secured/room'); 
var stompClient = Stomp.over(socket);
var sessionId = "";

stompClient.connect({}, function (frame) {
    var url = stompClient.ws._transport.url;
    url = url.replace(
      "ws://localhost:8080/spring-security-mvc-socket/secured/room/",  "");
    url = url.replace("/websocket", "");
    url = url.replace(/^[0-9]+\//, "");
    console.log("Your current session is: " + url);
    sessionId = url;
}

We also access the supplied sessionId and append that to the “secured/room  URL mapping. This gives us the ability to dynamically and manually supply a user-specific subscription queue:

我们还访问所提供的sessionId并将其附加到”secured/roomURL映射中。这使我们有能力动态地和手动地提供一个用户特定的订阅队列:

stompClient.subscribe('secured/user/queue/specific-user' 
  + '-user' + that.sessionId, function (msgOut) {
     //handle messages
}

Once everything’s set up we should see:

一旦一切准备就绪,我们应该看到。

 

Specific Sockets Specific

And in our server console:

而在我们的服务器控制台。

Specific Sockets Terminal

6. Conclusion

6.结论

Check out the official Spring blog and the official documentation for more information about this topic.

请查看Spring官方博客官方文档,了解有关该主题的更多信息。

As always, the code samples used in this article is available over on GitHub.

一如既往,本文中使用的代码样本可在GitHub上获得超过