使用Spring Boot实现带有STOMP的WebSocket实现

本文概述

  • WebSocket和STOMP协议
  • 服务器端:Spring Boot和WebSockets
  • 构建WebSocket客户端
  • 保护WebSockets
  • SockJS后备选项
  • WebSocket正在生产中?
WebSocket协议是使你的应用程序处理实时消息的方法之一。最常见的替代方法是长轮询和服务器发送事件。这些解决方案中的每一个都有其优点和缺点。在本文中, 我将向你展示如何使用Spring Boot Framework实现WebSocket。我将介绍服务器端和客户端的设置, 并且我们将使用基于WebSocket协议的STOMP相互通信。
服务器端将完全使用Java进行编码。但是, 对于客户端, 我将展示用Java和JavaScript(SockJS)编写的代码片段, 因为通常WebSockets客户端嵌入在前端应用程序中。该代码示例将演示如何使用pub-sub模型向多个用户广播消息, 以及如何仅向单个用户发送消息。在本文的另一部分中, 我将简要讨论如何保护WebSocket的安全以及如何确保即使环境不支持WebSocket协议, 基于WebSocket的解决方案仍可正常运行。
请注意, 保护WebSockets的主题此处仅作简单介绍, 因为它是单独撰写文章的足够复杂的主题。由于这个原因, 以及我在生产中的WebSocket中涉及的其他几个因素?最后, 我建议你在生产中使用此设置之前进行修改, 直到最后准备好具有安全措施的可用于生产的设置。
WebSocket和STOMP协议 WebSocket协议允许你在应用程序之间实现双向通信。重要的是要知道HTTP仅用于初始握手。发生这种情况之后, HTTP连接将升级为WebSocket使用的新打开的TCP / IP连接。
WebSocket协议是相当底层的协议。它定义了字节流如何转换为帧。框架可以包含文本或二进制消息。因为消息本身不提供有关如何路由或处理消息的任何其他信息, 所以如果不编写其他代码就很难实现更复杂的应用程序。幸运的是, WebSocket规范允许使用在更高的应用程序级别上运行的子协议。 Spring框架支持的其中之一是STOMP。
STOMP是一种基于文本的简单消息传递协议, 最初是为Ruby, Python和Perl等脚本语言创建的, 以连接到企业消息代理。借助STOMP, 以不同语言开发的客户和经纪人可以相互收发消息。 WebSocket协议有时称为Web的TCP。类似地, STOMP称为Web的HTTP。它定义了一些映射到WebSockets框架的框架类型, 例如CONNECT, SUBSCRIBE, UNSUBSCRIBE, ACK或SEND。一方面, 这些命令非常便于管理通信, 另一方面, 它们使我们能够实现具有更复杂功能(例如消息确认)的解决方案。
服务器端:Spring Boot和WebSockets 为了构建WebSocket服务器端, 我们将利用Spring Boot框架来显着加快Java中独立和Web应用程序的开发。 Spring Boot包含spring-WebSocket模块, 该模块与Java WebSocket API标准(JSR-356)兼容。
使用Spring Boot实现WebSocket服务器端并不是一项非常复杂的任务, 仅包括几个步骤, 我们将一步一步地进行介绍。
步骤1.首先, 我们需要添加WebSocket库依赖项。
< dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-websocket< /artifactId> < /dependency>

如果计划将JSON格式用于传输的消息, 则可能还需要包括GSON或Jackson依赖项。你很有可能还需要一个安全框架, 例如Spring Security。
步骤2.然后, 我们可以配置Spring以启用WebSocket和STOMP消息传递。
Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/mywebsockets") .setAllowedOrigins("mydomain.com").withSockJS(); }@Override public void configureMessageBroker(MessageBrokerRegistry config){ config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } }

方法configureMessageBroker做两件事:
  1. 创建具有一个或多个用于发送和接收消息的目的地的内存中消息代理。在上面的示例中, 定义了两个目标前缀:topic和queue。它们遵循的约定是, 要通过pub-sub模型传递给所有订阅的客户端的消息的目的地应以topic为前缀。另一方面, 私人消息的目的地通常以队列为前缀。
  2. 定义前缀应用程序, 该应用程序用于过滤由@MessageMapping注释的方法处理的目标, 你将在控制器中实现该方法。控制器在处理了消息之后, 会将其发送给代理。
使用Spring Boot实现带有STOMP的WebSocket实现

文章图片
服务器端如何处理消息(来源:Spring文档)
回到上面的代码段(可能你已经注意到对withSockJS()方法的调用), 它启用了SockJS后备选项。为了简短起见, 即使Internet浏览器不支持WebSocket协议, 也可以让我们的WebSockets工作。我将进一步详细讨论该主题。
还有一点需要澄清-为什么我们在端点上调用setAllowedOrigins()方法。经常需要这样做, 因为WebSocket和SockJS的默认行为是仅接受同源请求。因此, 如果你的客户端和服务器端使用不同的域, 则需要调用此方法以允许它们之间的通信。
步骤3.实现一个控制器来处理用户请求。它将接收到的消息广播到所有订阅给定主题的用户。
这是将消息发送到目标/ topic / news的示例方法。
@MessageMapping("/news") @SendTo("/topic/news") public void broadcastNews(@Payload String message) { return message; }

除了注释@SendTo外, 你还可以使用SimpMessagingTemplate, 你可以在控制器内部自动连线。
@MessageMapping("/news") public void broadcastNews(@Payload String message) { this.simpMessagingTemplate.convertAndSend("/topic/news", message) }

在后续步骤中, 你可能想要添加一些其他类来保护端点, 例如Spring Security框架中的ResourceServerConfigurerAdapter或WebSecurityConfigurerAdapter。此外, 实现消息模型通常很有益, 这样可以将传输的JSON映射到对象。
构建WebSocket客户端 实现客户是一个更加简单的任务。
步骤1.自动装配Spring STOMP客户端。
@Autowired private WebSocketStompClient stompClient;

步骤2.打开一个连接。
StompSessionHandler sessionHandler = new CustmStompSessionHandler(); StompSession stompSession = stompClient.connect(loggerServerQueueUrl, sessionHandler).get();

一旦完成, 就可以将消息发送到目的地。该消息将发送给所有订阅主题的用户。
stompSession.send("topic/greetings", "Hello new user");

也可以订阅消息。
session.subscribe("topic/greetings", this); @Override public void handleFrame(StompHeaders headers, Object payload) { Message msg = (Message) payload; logger.info("Received : " + msg.getText()+ " from : " + msg.getFrom()); }

有时仅需要将消息发送给专用用户(例如, 在进行聊天时)。然后, 客户端和服务器端必须使用专用于此私有对话的单独目标。可以通过将唯一标识符附加到通用目的地名称(例如, / queue / chat-user123)来创建目的地的名称。 HTTP会话或STOMP会话标识符可用于此目的。
Spring使发送私人消息变得更加容易。我们只需要使用@SendToUser注释Controller的方法。然后, 该目标将由依赖于会话标识符的UserDestinationMessageHandler处理。在客户端, 当客户端预订以/ user为前缀的目标时, 该目标将转换为该用户唯一的目标。在服务器端, 根据用户的主体解析用户目的地。
带有@SendToUser批注的示例服务器端代码:
@MessageMapping("/greetings") @SendToUser("/queue/greetings") public String reply(@Payload String message, Principal user) { return"Hello " + message; }

或者, 你可以使用SimpMessagingTemplate:
String username = ... this.simpMessagingTemplate.convertAndSendToUser(); username, "/queue/greetings", "Hello " + username);

现在, 让我们看一下如何实现一个JavaScript(SockJS)客户端, 该客户端能够接收上面示例中的Java代码发送的私人消息。值得一提的是, WebSockets是HTML5规范的一部分, 并且受大多数现代浏览器的支持(Internet Explorer从版本10开始就支持它们)。
function connect() { var socket = new SockJS('/greetings'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/user/queue/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).name); }); }); }function sendName() { stompClient.send("/app/greetings", {}, $("#name").val()); }

你可能已经注意到, 要接收私人消息, 客户端需要订阅以/ user为前缀的常规目标/ queue / greetings。它不必打扰任何唯一的标识符。但是, 客户端需要先登录到应用程序, 因此服务器端的Principal对象被初始化。
保护WebSockets 许多Web应用程序使用基于cookie的身份验证。例如, 我们可以使用Spring Security来限制对某些页面的访问, 或者仅对登录用户限制对Controllers的访问。然后, 通过基于cookie的HTTP会话维护用户安全上下文, 该会话随后与为该用户创建的WebSocket或SockJS会话相关联。 WebSockets端点可以像其他任何请求一样受到保护, 例如在Spring的WebSecurityConfigurerAdapter中。
如今, Web应用程序经常使用REST API作为其后端, 并使用OAuth / JWT令牌进行用户身份验证和授权。 WebSocket协议没有描述服务器在HTTP握手期间应如何对客户端进行身份验证。实际上, 为此目的使用标准的HTTP标头(例如, 授权)。不幸的是, 并非所有STOMP客户端都支持它。 Spring Java的STOMP客户端允许设置握手标头:
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add(principalRequestHeader, principalRequestValue);

但是SockJS JavaScript客户端不支持通过SockJS请求发送授权标头。但是, 它允许发送可用于传递令牌的查询参数。这种方法需要在服务器端编写自定义代码, 该代码将从查询参数中读取令牌并对其进行验证。确保令牌不与请求一起记录(或日志受到良好的保护)也很重要, 因为这可能会导致严重的安全冲突。
SockJS后备选项 与WebSocket的集成可能并不总是很顺利。某些浏览器(例如IE 9)不支持WebSocket。而且, 限制性代理可能无法执行HTTP升级, 或者它们会断开打开时间过长的连接。在这种情况下, SockJS可以提供??帮助。
SockJS传输分为三大类:WebSockets, HTTP流和HTTP长轮询。通信开始于SockJS发送GET / info以从服务器获取基本信息。根据响应, SockJS决定要使用的传输方式。首选是WebSockets。如果不支持它们, 则在可能的情况下使用流式传输。如果此选项也不可行, 则选择轮询作为传输方法。
WebSocket正在生产中? 尽管此设置有效, 但它不是” 最佳” 。 Spring Boot允许你使用具有STOMP协议的任何成熟的消息传递系统(例如ActiveMQ, RabbitMQ), 并且外部代理可能比我们使用的简单代理支持更多的STOMP操作(例如确认, 收据)。 WebSocket上的STOMP提供了有关WebSocket和STOMP协议的有趣信息。它列出了处理STOMP协议的消息传递系统, 并且可能是在生产中使用的更好的解决方案。特别是如果由于大量请求而需要对消息代理进行群集。 (Spring的简单消息代理不适合用于群集。)然后, 除了启用WebSocketConfig中的简单代理之外, 还需要启用Stomp代理中继, 以将消息转发到外部消息代理或从外部消息代理转发。综上所述, 外部消息代理可以帮助你构建更可扩展且更可靠的解决方案。
【使用Spring Boot实现带有STOMP的WebSocket实现】如果你准备好继续进行Java开发人员探索Spring Boot的旅程, 请尝试阅读接下来的使用Spring Boot for OAuth2和JWT REST Protection。

    推荐阅读