STOMP
WebSocket协议定义了两种类型的消息(文本和二进制),但它们的内容是未定义的。该协议规定了一种机制,允许客户端和服务器协商一个子协议(即更高层次的通信协议),在WebSocket之上使用该子协议来规定各自可以发送哪些类型的消息、每种消息的格式以及消息的内容等。使用子协议是可选的,但无论如何,客户端和服务器都需要就某种定义消息内容的协议达成一致。
部分总结
📄️ 概述
STOMP(Simple Text Oriented Messaging Protocol,简单文本导向的消息协议)最初是为脚本语言(如Ruby、Python和Perl)设计的,用于连接企业消息代理服务器。它旨在解决一组常用的消息传递模式中的基本问题。STOMP可以通过任何可靠的双向流式网络协议进行传输,例如TCP和WebSocket。尽管STOMP是一种基于文本的协议,但消息的有效载荷可以是文本形式,也可以是二进制形式。
📄️ 好处;益处
使用STOMP作为子协议,可以让Spring框架和Spring Security提供更加丰富的编程模型,相较于直接使用原始的WebSockets而言。同样的道理也适用于HTTP与原始TCP的对比,HTTP让Spring MVC和其他Web框架能够提供丰富的功能。以下是其中的一些优势:
📄️ 启用STOMP
spring-messaging 和 spring-websocket 模块提供了对 STOMP 的支持。一旦你添加了这些依赖,就可以通过 WebSocket 暴露一个 STOMP 端点,如下例所示:
📄️ WebSocket传输
本节解释如何配置底层的WebSocket服务器传输机制。
📄️ 消息流
一旦暴露了一个STOMP端点,Spring应用程序就成为了连接客户端的STOMP代理。本节描述了服务器端消息的流转过程。
📄️ 带注释的控制器
应用程序可以使用带有注解的@Controller类来处理来自客户端的消息。此类可以声明@MessageMapping、@SubscribeMapping和@ExceptionHandler方法,具体内容将在以下主题中介绍:
📄️ 发送消息
如果你想从应用程序的任何部分向已连接的客户端发送消息会怎么样?任何应用程序组件都可以向brokerChannel发送消息。最简单的方法是注入一个SimpMessagingTemplate并使用它来发送消息。通常,你会按类型进行注入,如下例所示:
📄️ 简单代理
内置的简单消息代理负责处理来自客户端的订阅请求,将这些请求存储在内存中,并将消息广播给具有匹配目的地的已连接客户端。该代理支持类似路径的目的地,包括对Ant风格目的地模式的订阅。
📄️ 外部经纪人
简单Broker非常适合入门使用,但它只支持一部分STOMP命令(不支持acks、receipts以及一些其他功能),依赖于简单的消息发送循环,也不适合进行集群部署。作为替代方案,你可以将应用程序升级到使用功能更齐全的消息Broker。
📄️ 连接经纪商
STOMP 代理中继与 broker 保持单一的“系统”TCP连接。该连接仅用于服务器端应用程序发送的消息,不用于接收消息。你可以配置此连接的 STOMP 凭据(即 STOMP 帧中的登录和密码头信息)。在 XML 命名空间和 Java 配置中,这些凭据分别以 systemLogin 和 systemPasscode 的属性形式进行定义,默认值分别为 guest 和 guest。
📄️ 点作为分隔符
当消息被路由到@MessageMapping方法时,它们会与AntPathMatcher进行匹配。默认情况下,模式预期使用斜杠(/)作为分隔符。这在Web应用程序中是一个良好的惯例,类似于HTTP URL。然而,如果您更习惯于消息传递的惯例,可以切换为使用点(.)作为分隔符。
📄️ 认证
每个通过WebSocket进行的消息会话都以一个HTTP请求开始。这个请求可以是用于升级到WebSocket的请求(即WebSocket握手),或者在SockJS回退机制的情况下,是一系列SockJS HTTP传输请求。
📄️ 令牌认证(Token Authentication)
Spring Security OAuth 提供基于令牌的安全性支持,包括 JSON Web Token(JWT)。您可以将其作为 Web 应用程序中的身份验证机制使用,包括在 WebSocket 交互中的 STOMP 协议中,正如前一节所描述的那样(即通过基于 cookie 的会话来维护用户身份)。
📄️ 授权;特许;批准
Spring Security提供了WebSocket子协议授权功能,该功能使用ChannelInterceptor根据消息中的用户头部信息(user header)来验证授权。同时,Spring Session也支持WebSocket集成,确保在WebSocket会话仍然活跃的情况下,用户的HTTP会话不会过期。
📄️ 用户目的地
应用程序可以发送针对特定用户的消息,Spring的STOMP支持能够识别以/user/为前缀的目的地来处理这些消息。例如,客户端可能会订阅/user/queue/position-updates目的地。UserDestinationMessageHandler负责处理这个目的地,并将其转换为与用户会话唯一对应的目的地(如/queue/position-updates-user123)。这种方式既提供了订阅通用名称目的地的便利性,同时确保了不会与其他订阅相同目的地的用户发生冲突,从而让每个用户都能接收到独家的股票持仓更新信息。
📄️ 消息顺序
来自代理的消息被发布到clientOutboundChannel,然后从那里写入WebSocket会话中。由于该通道由ThreadPoolExecutor支持,消息在不同的线程中被处理,因此客户端接收到的消息顺序可能与发布的实际顺序不完全一致。
📄️ 活动
通过实现Spring的ApplicationListener接口,可以发布和接收多个ApplicationContext事件:
📄️ 拦截;截获;截球
事件会为STOMP连接的生命周期提供通知,但不会为每条客户端消息都提供通知。应用程序还可以注册一个ChannelInterceptor来拦截处理链中的任何消息和任何环节。以下示例展示了如何拦截来自客户端的传入消息:
📄️ STOMP 客户端
Spring提供了基于WebSocket的STOMP客户端和基于TCP的STOMP客户端。
📄️ WebSocket 范围(WebSocket Scope)
每个WebSocket会话都有一组属性的映射(map)。这个映射作为头部信息附加在进入服务器的客户端消息中,可以从控制器方法中访问到,如下例所示:
📄️ 性能;表现
在性能方面,不存在一劳永逸的解决方案。许多因素都会影响性能,包括消息的大小和数量、应用程序方法是否执行需要阻塞的操作,以及外部因素(如网络速度等其他问题)。本节的目的是提供可用的配置选项概述,并对如何进行扩展进行一些思考。
📄️ 监控
当你使用@EnableWebSocketMessageBroker或websocket:message-broker时,关键的基础架构组件会自动收集统计数据和计数器,这些数据能够提供关于应用程序内部状态的重要见解。配置还声明了一个类型为WebSocketMessageBrokerStats的Bean,该Bean将所有可用信息汇集到一处,并默认每30分钟在INFO级别记录一次日志。通过Spring的MBeanExporter,可以将这个Bean导出到JMX中,以便在运行时进行查看(例如,通过JDK的jconsole)。以下列表总结了这些可用的信息:
📄️ 测试
当你使用Spring的STOMP-over-WebSocket支持来测试应用程序时,主要有两种方法。第一种是编写服务器端测试来验证控制器及其带注释的消息处理方法的功能性。第二种是编写完整的端到端测试,这涉及到运行客户端和服务器。