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