这部分参考文档包括对Servlet堆栈的支持,包括原始WebSocket交互的WebSocket消息传递,通过SockJS的WebSocket仿真,以及通过STOMP作为WebSocket上的子协议的pub-sub消息传递。
4.1。介绍
WebSocket协议提供了一种标准化方法,可通过单个TCP连接在客户端和服务器之间建立全双工双向通信通道。它是来自HTTP的不同TCP协议,但设计为使用端口80和443通过HTTP工作,并允许重用现有防火墙规则。
WebSocket交互以HTTP请求开始,该HTTP请求使用HTTP 标头升级,或者在这种情况下切换到WebSocket协议:
具有WebSocket支持的服务器返回:而不是通常的200状态代码:
成功握手后,HTTP升级请求下的TCP套接字保持打开状态,客户端和服务器都可以继续发送和接收消息。
有关WebSockets如何工作的完整介绍超出了本文档的范围。请阅读RFC 6455,HTML5的WebSocket章节,或Web上的许多介绍和教程之一。
请注意,如果WebSocket服务器在Web服务器(例如nginx)后面运行,您可能需要将其配置为将WebSocket升级请求传递到WebSocket服务器。同样,如果应用程序在云环境中运行,请检查与WebSocket支持相关的云提供程序的说明。
4.1.1。HTTP vs WebSocket
即使WebSocket被设计为HTTP兼容并以HTTP请求开始,但重要的是要理解这两种协议会导致非常不同的体系结构和应用程序编程模型。
在HTTP和REST中,应用程序被建模为多个URL。要与应用程序客户端进行交互,请访问这些URL,请求 - 响应样式。服务器根据HTTP URL,方法和标头将请求路由到适当的处理程序。
相比之下,在WebSockets中,通常只有一个URL用于初始连接,随后所有应用程序消息都在同一TCP连接上流动。这指向完全不同的异步,事件驱动的消息传递体系结构。
WebSocket也是一种低级传输协议,与HTTP不同,它没有规定消息内容的任何语义。这意味着除非客户端和服务器就消息语义达成一致,否则无法路由或处理消息。
WebSocket客户端和服务器可以通过HTTP握手请求上的标头协商使用更高级别的消息传递协议(例如STOMP),或者在没有它们的情况下,他们需要提出自己的约定。
4.1.2。什么时候用?
WebSockets可以使网页动态和交互。但是,在许多情况下,Ajax和HTTP流和/或长轮询的组合可以提供简单有效的解决方案。
例如,新闻,邮件和社交订阅源需要动态更新,但每隔几分钟就可以完全正常更新。另一方面,协作,游戏和财务应用程序需要更接近实时。
仅延迟不是决定因素。如果消息量相对较低(例如,监视网络故障),则HTTP流式传输或轮询可以提供有效的解决方案。它是低延迟,高频率和高容量的组合,是使用WebSocket的**选择。
还要记住,通过Internet,控制之外的限制性代理可能会阻止WebSocket交互,因为它们未配置为传递 标头,或者因为它们关闭看似空闲的长期连接?这意味着将WebSocket用于防火墙内的内部应用程序比面向公众的应用程序更直接。
4.2。WebSocket API
Spring Framework提供了一个WebSocket API,可用于编写处理WebSocket消息的客户端和服务器端应用程序。
4.2.1。WebSocketHandler
创建的WebSocket服务器是为实现简单或更可能要么延长或:
有专门的WebSocket Java-config和XML名称空间支持,用于将上述WebSocket处理程序映射到特定的URL:
XML配置等效:
以上内容适用于Spring MVC应用程序,应包含在的配置中。但是,Spring的WebSocket支持不依赖于Spring MVC。 在的帮助下将其集成到其他HTTP服务环境中 相对简单。
4.2.2。WebSocket握手
自定义初始HTTP WebSocket握手请求的最简单方法是通过a ,它暴露握手方法的“之前”和“之后”。这样的拦截器可用于阻止握手或使任何属性可用于。例如,有一个内置拦截器,用于将HTTP会话属性传递给WebSocket会话:
并且XML配置等效:
更高级的选项是扩展执行WebSocket握手步骤,包括验证客户端来源,协商子协议等。如果应用程序需要配置自定义以适应WebSocket服务器引擎和尚不支持的版本,则应用程序可能还需要使用此选项(有关此主题的更多信息,请参阅)。Java-config和XML命名空间都可以配置自定义 。
4.2.3。部署
Spring WebSocket API易于集成到Spring MVC应用程序中,其中既提供HTTP WebSocket握手,也提供其他HTTP请求。通过调用也可以轻松地集成到其他HTTP处理场景中。这很方便易懂。但是,特殊注意事项适用于JSR-356运行时。
Java WebSocket API(JSR-356)提供了两种部署机制。第一个涉及启动时的Servlet容器类路径扫描(Servlet 3功能); 另一个是在Servlet容器初始化时使用的注册API。这些机制都不能使用单个“前端控制器”进行所有HTTP处理 - 包括WebSocket握手和所有其他HTTP请求 - 例如Spring MVC 。
这是JSR-356的一个重要限制,Spring的WebSocket支持即使在JSR-356运行时运行时也能解决特定于服务器的问题。目前,Tomcat,Jetty,GlassFish,WebLogic,WebSphere和Undertow(以及WildFly)都有这样的策略。
第二个考虑因素是具有JSR-356支持的Servlet容器应该执行(SCI)扫描,这可能会减慢应用程序启动速度,在某些情况下会显着降低。如果在升级到支持JSR-356的Servlet容器版本后观察到重大影响,则应该可以通过使用以下元素选择性地启用或禁用Web片段(和SCI扫描):
然后,您可以根据需要有选择地启用Web片段,例如Spring自己 提供对Servlet 3 Java初始化API的支持(如果需要):
4.2.4。服务器配置
每个底层WebSocket引擎都公开控制运行时特性的配置属性,例如消息缓冲区大小,空闲超时等。
对于Tomcat,WildFly和GlassFish ,在WebSocket Java配置中添加一个:
或WebSocket XML命名空间:
对于Jetty,您需要提供预配置的Jetty 并通过WebSocket Java配置将其插入Spring :
或WebSocket XML命名空间:
4.2.5。允许来源
从Spring Framework 4.1.5开始,WebSocket和SockJS的默认行为是仅接受相同的原始请求。也可以允许所有或指定的起源列表。此检查主要是为浏览器客户端设计的。没有什么可以阻止其他类型的客户端修改标头值(有关更多详细信息,请参阅 )。
3种可能的行为是:
WebSocket和SockJS允许的起源可以配置如下所示:
XML配置等效:
4.3。SockJS后备
在公共Internet上,受控制之外的限制性代理可能会阻止WebSocket交互,因为它们未配置为传递标头,或者因为它们关闭看似空闲的长期连接。
这个问题的解决方案是WebSocket仿真,即首先尝试使用WebSocket,然后依靠基于HTTP的技术来模拟WebSocket交互并公开相同的应用程序级API。
在Servlet堆栈上,Spring Framework为SockJS协议提供服务器(以及客户端)支持。
4.3.1。概观
SockJS的目标是让应用程序使用WebSocket API,但在运行时必要时可以回退到非WebSocket替代品,即无需更改应用程序代码。
SockJS包括:
SockJS专为在浏览器中使用而设计。它竭尽全力使用各种技术支持各种浏览器版本。有关SockJS传输类型和浏览器的完整列表,请参阅页面。传输分为3大类:WebSocket,HTTP Streaming和HTTP Long Polling。有关这些类别的概述,请参阅 。
SockJS客户端首先发送以从服务器获取基本信息。之后,它必须决定使用什么传输。如果可能,使用WebSocket。如果没有,在大多数浏览器中至少有一个HTTP流选项,如果没有,则使用HTTP(长)轮询。
所有传输请求都具有以下URL结构:
WebSocket传输只需要一个HTTP请求即可进行WebSocket握手。之后的所有消息都在该套接字上交换。
HTTP传输需要更多请求。例如,Ajax / XHR流依赖于一个长期运行的服务器到客户端消息请求以及针对客户端到服务器消息的额外HTTP POST请求。长轮询是类似的,除了它在每个服务器到客户端发送之后结束当前请求。
SockJS增加了最小的消息框架。例如,服务器最初发送字母o(“打开”帧),消息作为[“message1”,“message2”](JSON编码数组)发送,字母h(“心跳”帧)如果没有消息流默认为25秒,字母c(“关闭”框架)关闭会话。
要了解更多信息,请在浏览器中运行示例并观察HTTP请求。SockJS客户端允许修复传输列表,因此可以一次查看每个传输。SockJS客户端还提供了一个调试标志,可在浏览器控制台中启用有用的消息。在服务器端启用 日志记录。有关更多详细信息,请参阅SockJS协议 。
4.3.2。启用SockJS
通过Java配置可以轻松启用SockJS:
和XML配置等价:
以上内容适用于Spring MVC应用程序,应包含在的配置中。但是,Spring的WebSocket和SockJS支持并不依赖于Spring MVC。在的帮助下,集成到其他HTTP服务环境中相对简单 。
在浏览器端,应用程序可以使用模拟W3C WebSocket API 的 (版本1.0.x)并与服务器通信,以根据其运行的浏览器选择**传输选项。查看 页面和浏览器支持的传输类型列表。客户端还提供了几个配置选项,例如,指定要包含的传输。
4.3.3。IE 8,9
Internet Explorer 8和9在一段时间内仍然很常见。他们是拥有SockJS的关键原因。本节介绍在这些浏览器中运行的重要注意事项。
SockJS客户端通过Microsoft的支持IE 8和9中的Ajax / XHR流 。这适用于域,但不支持发送cookie。Cookie通常对Java应用程序至关重要。但是,由于SockJS客户端可以与许多服务器类型(不仅仅是Java)一起使用,因此需要知道cookie是否重要。如果是这样,SockJS客户端更喜欢使用Ajax / XHR进行流式传输,否则它依赖于基于iframe的技术。
最先从SockJS客户端请求是针对可能影响客户的传输选择信息的请求。其中一个细节是服务器应用程序是否依赖于cookie,例如用于身份验证或使用粘性会话进行群集。Spring的SockJS支持包括一个名为的属性。默认情况下启用它,因为大多数Java应用程序都依赖于 cookie。如果您的应用程序不需要它,您可以关闭此选项,SockJS客户端应在IE 8和9中选择。
如果你使用基于iframe的运输,并且在任何情况下,这是好事,知道浏览器可以指示通过设置HTTP响应标题来阻止特定网页上的使用iframe 来, 或。这用于防止 。
如果您的应用程序添加响应标头(应该!)并依赖于基于iframe的传输,则需要将标头值设置为 或。除此之外,Spring SockJS支持还需要知道SockJS客户端的位置,因为它是从iframe加载的。默认情况下,iframe设置为从CDN位置下载SockJS客户端。最好将此选项配置为与应用程序源相同的URL。
在Java配置中,这可以如下所示完成。XML命名空间通过元素提供类似的选项:
4.3.4。心跳
SockJS协议要求服务器发送心跳消息以阻止代理断定连接挂起。Spring SockJS配置有一个名为的属性,可用于自定义频率。默认情况下,假设在该连接上没有发送其他消息,则在25秒后发送心跳。此25秒值符合以下 对公共Internet应用程序的。
Spring SockJS支持还允许配置用于调度心跳任务。任务计划程序由线程池支持,默认设置基于可用处理器的数量。应用程序应考虑根据其特定需求自定义设置。
4.3.5。客户端断开连接
HTTP流式传输和HTTP长轮询SockJS传输要求连接保持打开时间比平时长。有关这些技术的概述,请参阅 。
在Servlet容器中,这是通过Servlet 3异步支持完成的,它允许退出Servlet容器线程处理请求并继续写入来自另一个线程的响应。
一个特定的问题是Servlet API不为已经消失的客户端提供通知,请参阅。但是,Servlet容器在后续尝试写入响应时引发异常。由于Spring的SockJS服务支持服务器发送的心跳(默认情况下每25秒),这意味着如果更频繁地发送消息,通常会在该时间段或更早的时间内检测到客户端断开连接。
4.3.6。SockJS和CORS
如果允许跨源请求(请参阅),则SockJS协议使用CORS在XHR流和轮询传输中进行跨域支持。因此,除非检测到响应中存在CORS头,否则将自动添加CORS头。因此,如果应用程序已配置为提供CORS支持,例如通过Servlet过滤器,Spring的SockJsService将跳过此部分。
也可以通过Spring的SockJsService中的属性禁用这些CORS头 的添加。
以下是SockJS预期的标题和值列表:
对于确切的执行看到的还有在源代码中枚举。
或者,如果CORS配置允许它考虑使用SockJS端点前缀排除URL,从而让Spring 处理它。
4.3.7。SockJsClient
提供了SockJS Java客户端,以便在不使用浏览器的情况下连接到远程SockJS端点。当需要通过公共网络在2个服务器之间进行双向通信时,即在网络代理可能妨碍使用WebSocket协议的情况下,这尤其有用。SockJS Java客户端对于测试目的也非常有用,例如模拟大量并发用户。
SockJS Java客户端支持“websocket”,“xhr-streaming”和“xhr-polling”传输。其余的仅适用于浏览器。
的可被配置成与:
根据定义,支持“xhr-streaming”和“xhr-polling”,因为从客户端的角度来看,除了用于连接服务器的URL之外没有其他区别。目前有两种实现方式:
下面的示例显示了如何创建SockJS客户端并连接到SockJS端点:
要使用SockJsClient模拟大量并发用户,您需要配置底层HTTP客户端(用于XHR传输)以允许足够数量的连接和线程。例如Jetty:
还要考虑自定义这些服务器端SockJS相关属性(有关详细信息,请参阅Javadoc):
4.4。STOMP
WebSocket协议定义了两种类型的消息,文本和二进制,但它们的内容是未定义的。定义了客户端和服务器协商子协议的机制 - 即更高级别的消息传递协议,在WebSocket之上使用以定义每个消息可以发送什么类型的消息,每个消息的格式和内容是什么,等等上。子协议的使用是可选的,但无论是客户端还是服务器都需要就定义消息内容的某些协议达成一致。
4.4.1。概观
是一种简单的,面向文本的消息传递协议,最初是为Ruby,Python和Perl等脚本语言创建的,用于连接企业消息代理。它旨在解决常用消息传递模式的最小子集。STOMP可用于任何可靠的双向流网络协议,如TCP和WebSocket。虽然STOMP是面向文本的协议,但消息有效负载可以是文本或二进制。
STOMP是一种基于帧的协议,其帧在HTTP上建模。STOMP框架的结构:
客户端可以使用SEND或SUBSCRIBE命令发送或订阅消息以及“目标”标头,该标头描述消息的内容以及应由谁接收消息。这启用了一个简单的发布 - 订阅机制,可用于通过代理将消息发送到其他连接的客户端,或者向服务器发送消息以请求执行某些工作。
使用Spring的STOMP支持时,Spring WebSocket应用程序充当客户端的STOMP代理。消息被路由到消息处理方法或简单的内存中间代理,该代理跟踪订阅并向订阅用户广播消息。您还可以将Spring配置为使用专用的STOMP代理(例如RabbitMQ,ActiveMQ等)来实现消息的实际广播。在这种情况下,Spring维护与代理的TCP连接,向其中继消息,并将消息从其传递到连接的WebSocket客户端。因此,Spring Web应用程序可以依赖于基于HTTP的统一安全性,通用验证以及熟悉的编程模型消息处理工作。
以下是订阅接收股票报价的客户的示例,服务器可以例如通过计划任务通过a 向经纪人发送消息来周期性地发出报价:
以下是客户端发送交易请求的示例,服务器可以通过该方法处理该交易请求,之后,在执行之后,向客户端广播交易确认消息和详细信息:
在STOMP规范中故意将目的地的含义保持不透明。它可以是任何字符串,完全取决于STOMP服务器,以定义它们支持的目标语义和语法。然而,很常见的是,目标是类似路径的字符串,其中暗示发布 - 订阅(一对多)并且暗示点对点(一对一)消息交换。
STOMP服务器可以使用MESSAGE命令向所有订户广播消息。以下是服务器向订阅客户端发送股票报价的示例:
知道服务器无法发送未经请求的消息非常重要。来自服务器的所有消息必须响应特定的客户端订阅,并且服务器消息的“subscription-id”头必须与客户端订阅的“id”头匹配。
以上概述旨在提供对STOMP协议的最基本的了解。建议完整地查看协议 。
4.4.2。优点
使用STOMP作为子协议使Spring Framework和Spring Security能够提供比使用原始WebSocket更丰富的编程模型。关于HTTP与原始TCP的关系以及它如何使Spring MVC和其他Web框架能够提供丰富的功能,可以做出同样的观点。以下是一系列好处:
4.4.3。启用STOMP
STOMP在WebSocket的支持是可用的和 模块。一旦拥有了这些依赖项,就可以通过带有 WebSocket公开STOMP端点,如下所示:
XML中的相同配置:
要从浏览器连接,对于SockJS,您可以使用 。对于STOMP,许多应用程序使用了库(也称为stomp.js),该库功能齐全,已在生产中使用多年,但不再维护。目前, 是该库中最活跃的维护和不断发展的后继者,下面的示例代码基于它:
或者如果通过WebSocket连接(没有SockJS):
请注意,上面不需要指定和标题。即使它确实如此,它们也会在服务器端被忽略或被覆盖。有关的详细信息,请参阅“ 和 验证”部分。
有关更多示例代码,请参阅:
4.4.4。消息流
一旦暴露了STOMP端点,Spring应用程序就成为连接客户端的STOMP代理。本节介绍服务器端的消息流。
该模块包含对源自消息传递应用程序的基础支持,后来被提取并整合到Spring Framework中,以便在许多和应用程序场景中得到更广泛的使用 。下面列出了一些可用的消息传递抽象:
Java配置(即)和XML命名空间配置(即)都使用上述组件来组装消息工作流。下图显示了启用简单的内置消息代理时使用的组件:
上图中有3个消息通道:
下图显示了配置外部代理(例如RabbitMQ)以管理订阅和广播消息时使用的组件:
上图中的主要区别是使用“代理中继”通过TCP将消息传递到外部STOMP代理,以及将消息从代理传递到订阅的客户端。
当从WebSocket connectin接收消息时,它们被解码为STOMP帧,然后变成Spring 表示,并发送到进行进一步处理。例如STOMP消息,其目的地标题开头可被路由到在注释的控制器的方法,而和消息可以被直接路由到消息代理。
从客户端处理STOMP消息的带注释的消息可以通过消息代理向消息代理发送消息,并且代理将通过消息向匹配的订阅者广播消息。相同的控制器也可以响应HTTP请求执行相同的操作,因此客户端可以执行HTTP POST,然后方法可以向消息代理发送消息以向订阅的客户端广播。
让我们通过一个简单的例子来追踪流程。鉴于以下服务器设置:
下一节提供了有关注释方法的更多详细信息,包括支持的参数类型和返回值。
4.4.5。带注释的控制器
应用程序可以使用带注释的类来处理来自客户端的消息。这些类可以声明,和 方法,如下所述。
该批注可在方法基于他们的目的地将消息路由使用。它在方法级别和类型级别受支持。在类型级别用于表示控制器中所有方法的共享映射。
默认情况下,目标映射应为Ant样式的路径模式,例如“/ foo *”,“/ foo / ”。模式包括对模板变量的支持,例如“/ foo / {id}”,可以使用方法参数引用。
方法可以使用以下参数进行灵活签名:
当方法返回一个值时,默认情况下,该值通过已配置的序列化为有效负载,然后作为a发送到 它向订阅者广播的位置。出站消息的目的地与入站消息的目的地相同,但前缀为。
您可以使用方法批注来自定义要将有效负载发送到的目标。也可以在类级别使用以共享发送消息的默认目标目标。是仅向与消息关联的用户发送消息的变体。有关详细信息,请参阅
方法的返回值可以用, 包装,或者以异步方式生成有效负载。
作为从方法返回有效负载的替代方法,您还可以使用the发送消息,这也是在封面下处理返回值的方式。请参阅。
的注释结合使用,以缩小的映射到订阅消息。在这种情况下, 注释指定目的地,同时仅指示对订阅消息的兴趣。
的方法,通常是没有任何不同 相对于映射和输入参数的方法。例如,您可以将它与类型级别组合以表示共享目标前缀,并且可以使用与任何@ MessageMapping`方法相同的。
关键的区别在于方法的返回值被序列化为有效载荷并且不是发送到“brokerChannel”而是发送到“clientOutboundChannel”,有效地直接回复到客户端而不是通过代理进行广播。这对于实现一次性请求 - 回复消息交换非常有用,并且永远不会保留订阅。此模式的常见方案是在必须加载和显示数据时应用程序初始化。
阿与也可以注释的方法在这种情况下的返回值发送到与所述显式指定的目标目的地。
应用程序可以使用方法来处理方法中的异常 。感兴趣的异常可以在注释本身中声明,或者如果要获取对异常实例的访问权限,则可以通过方法参数声明:
方法支持灵活的方法签名,并支持相同的方法参数类型和返回值作为方法。
通常,方法适用于声明它们的类(或类层次结构)。如果您希望这些方法在控制器之间全局应用,则可以在标记为的类中声明它们。这与Spring MVC中的相当。
4.4.6。发送信息
如果要从应用程序的任何部分向连接的客户端发送消息,该怎么办?任何应用程序组件都可以向其发送消息。最简单的方法是注入,并使用它来发送消息。通常,应该很容易按类型注入,例如:
但如果存在相同类型的另一个bean,它也可以通过其名称“brokerMessagingTemplate”进行限定。
4.4.7。简单的经纪人
内置的简单消息代理处理来自客户端的订阅请求,将它们存储在内存中,并将消息广播到具有匹配目标的连接客户端。代理支持类似路径的目标,包括对Ant样式目标模式的订阅。
4.4.8。外部经纪人
简单的代理非常适合入门,但仅支持STOMP命令的子集(例如,没有ack,收据等),依赖于简单的消息发送循环,并且不适合于群集。作为替代方案,应用程序可以升级到使用功能齐全的消息代理。
检查STOMP文档以查找您选择的消息代理(例如 , 等),安装代理,并在启用STOMP支持的情况下运行它。然后在Spring配置中启用STOMP代理中继而不是简单代理。
以下是启用功能齐全的代理的示例配置:
XML配置等效:
上述配置中的“STOMP代理中继”是Spring ,它通过将消息转发到外部消息代理来处理消息。为此,它建立到代理的TCP连接,将所有消息转发给它,然后通过其WebSocket会话将从代理接收的所有消息转发给客户端。从本质上讲,它充当“转发”,可以在两个方向上转发消息。
此外,应用程序组件(例如HTTP请求处理方法,业务服务等)也可以向代理中继,如,以便向订阅的WebSocket客户端广播消息。
实际上,代理中继实现了健壮且可扩展的消息广播。
4.4.9。连接到经纪人
STOMP代理中继维护与代理的单个“系统”TCP连接。此连接仅用于源自服务器端应用程序的消息,而不用于接收消息。您可以为此连接配置STOMP凭据,即STOMP帧和标头。这在XML命名空间和Java配置中都显示为具有默认值/ 的 / properties 。
STOMP代理中继还为每个连接的WebSocket客户端创建单独的TCP连接。您可以配置STOMP凭据以用于代表客户端创建的所有TCP连接。这在XML命名空间和Java配置中都显示为具有默认值/ 的/ properties 。
STOMP代理中继还通过“系统”TCP连接向消息代理发送和接收心跳。您可以配置发送和接收心跳的间隔(默认情况下每个10秒)。如果与代理的连接丢失,代理中继将继续尝试每5秒重新连接一次,直到成功为止。
任何Spring bean都可以实现,以便在与代理的“系统”连接丢失并重新建立时接收通知。例如,股票报价服务广播股票报价可以在没有活动的“系统”连接时停止尝试发送消息。
默认情况下,STOMP代理中继始终连接,并在连接丢失时根据需要重新连接到同一主机和端口。如果您希望提供多个地址,则在每次尝试连接时,您都可以配置地址供应商,而不是固定的主机和端口。例如:
STOMP代理中继也可以配置属性。该属性的值将被设置为每个帧的标题,并且可能在例如云环境中是有用的,其中建立TCP连接的实际主机与提供基于云的STOMP服务的主机不同。
4.4.10。点作为分隔符
当消息路由到方法时,它们会匹配, 默认情况下,模式应使用斜杠“/”作为分隔符。这是Web应用程序中的一个很好的约定,类似于HTTP URL。但是,如果您更习惯于消息传递约定,则可以切换到使用点“。” 作为分隔符。
在Java配置中:
在XML中:
之后,控制器可以使用点“。” 作为方法中的分隔符:
客户端现在可以向其发送消息。
在上面的示例中,我们没有更改“代理中继”上的前缀,因为它们完全依赖于外部消息代理。检查您正在使用的代理的STOMP文档页面,以查看它为目标标头支持的约定。
另一方面,“简单代理”确实依赖于配置,因此如果您切换也将应用于代理的分隔符,并且将消息中的目标与订阅中的模式匹配。
4.4.11。认证
WebSocket消息传递会话中的每个STOMP都以HTTP请求开始 - 可以是升级到WebSockets的请求(即WebSocket握手),或者在SockJS回退一系列SockJS HTTP传输请求的情况下。
Web应用程序已经具有用于保护HTTP请求的身份验证和授权。通常,用户通过Spring Security使用某种机制(例如登录页面,HTTP基本身份验证或其他)进行身份验证。经过身份验证的用户的安全上下文保存在HTTP会话中,并与同一个基于cookie的会话中的后续请求相关联。
因此,对于WebSocket握手或SockJS HTTP传输请求,通常会有可通过身份验证的用户访问。Spring自动将该用户与为其创建的WebSocket或SockJS会话相关联,随后通过用户头与该会话上传输的所有STOMP消息相关联。
简而言之,典型的Web应用程序不需要做任何特殊的事情,而不仅仅是它已经为安全做的事情。用户在HTTP请求级别进行身份验证,并通过基于cookie的HTTP会话维护安全上下文,然后将该会话与为该用户创建的WebSocket或SockJS会话相关联,并在每次流经应用程序时生成用户标头。
请注意,STOMP协议在帧上具有“登录”和“密码”标头。这些最初设计用于并且仍然需要例如用于TCP上的STOMP。但是,对于STOMP over WebSocket,Spring默认忽略STOMP协议级别的授权标头,并假定用户已在HTTP传输级别进行了身份验证,并期望WebSocket或SockJS会话包含经过身份验证的用户。
4.4.12。令牌认证
支持基于令牌的安全性,包括JSON Web Token(JWT)。这可以用作Web应用程序中的身份验证机制,包括STOMP over WebSocket交互,就像上一节中所述,即通过基于cookie的会话维护身份。
同时,基于cookie的会话并不总是最适合,例如在不希望完全维护服务器端会话的应用程序中,或者在通常使用标头进行身份验证的移动应用程序中。
该 “没有规定该服务器可以在WebSocket的握手过程中验证客户端的任何特定的方式。” 实际上,浏览器客户端只能使用标准身份验证标头(即基本HTTP身份验证)或cookie,并且不能提供自定义标头。同样,SockJS JavaScript客户端不提供使用SockJS传输请求发送HTTP头的方法,请参阅 。相反,它确实允许发送可用于发送令牌但具有其自身缺点的查询参数,例如因为令牌可能无意中使用服务器日志中的URL进行了记录。
因此,希望避免使用cookie的应用程序可能无法在HTTP协议级别进行身份验证。他们可能更喜欢在STOMP消息传递协议级别使用标头进行身份验证,而不是使用Cookie。有两个简单的步骤可以做到这一点:
下面是注册自定义身份验证拦截器的示例服务器端配置。请注意,拦截器只需要在CONNECT上进行身份验证并设置用户头。Spring将记录并保存经过身份验证的用户,并将其与同一会话中的后续STOMP消息相关联:
另请注意,在使用Spring Security的邮件授权时,您需要确保在Spring Security之前订购身份验证配置。最好通过在自己的标记为 的实现中声明自定义拦截器来完成。
4.4.13。用户目的地
应用程序可以发送针对特定用户的消息,Spring的STOMP支持可识别为此目的而作为前缀的目标。例如,客户端可能订阅目标。该目的地将由该处理并且转换为用户会话唯一的目的地,例如。这提供了订阅一般命名的目的地的便利性,同时确保不与订阅相同目的地的其他用户发生冲突,使得每个用户可以接收唯一的库存位置更新。
在发送侧,可以将消息发送到目的地,例如,该目的地 将由一个或多个目的地翻译,一个目的地用于与用户相关联的每个会话。这允许应用程序中的任何组件发送针对特定用户的消息,而不必知道除其名称和通用目标之外的任何内容。通过注释和消息传递模板也支持这一点。
例如,消息处理方法可以向与通过注释处理的消息相关联的用户发送消息(在类级别上也支持共享公共目的地):
如果用户具有多个会话,则默认情况下,所有订阅给定目标的会话都是目标。但是,有时可能需要仅定位发送正在处理的消息的会话。这可以通过将属性设置为false 来完成,例如:
例如,还可以通过注入由Java配置或XML命名空间创建的消息,从任何应用程序组件向用户目标发送消息(如果需要,则bean名称是必需的):
在多应用程序服务器方案中,用户目标可能仍未解析,因为用户连接到不同的服务器。在这种情况下,您可以配置目标以广播未解析的消息,以便其他服务器有机会尝试。这可以通过做财产 在Java中的配置和该属性的XML元素。
4.4.14。事件和拦截
发布了几个事件(如下所列),可以通过实现Spring的接口来接收。
上述事件反映了STOMP连接生命周期中的点。它们并不意味着为客户端发送的每条消息提供通知。相反,应用程序可以注册a 来拦截每个传入和传出的STOMP消息。例如,拦截入站消息:
自定义可以使用或 访问有关邮件的信息。
请注意,与上面一样,可能已从客户端发送DISCONNECT消息,或者也可能在WebSocket会话关闭时自动生成。在某些情况下,拦截器可能会在每个会话中多次拦截此消息。对于多个断开连接事件,组件应该是幂等的。
4.4.15。STOMP客户端
Spring通过WebSocket客户端提供STOMP,通过TCP客户端提供STOMP。
要开始创建和配置:
在上面的例子可以替换为, 因为这也是一个实现。所述可以使用的WebSocket或基于HTTP的传输作为后备。有关更多详细信息,请参阅 。
接下来建立连接并为STOMP会话提供处理程序:
当会话准备好使用时,会通知处理程序:
建立会话后,可以发送任何有效负载,并使用配置的序列化:
您也可以订阅目的地。这些方法需要处理订阅消息的处理程序,并返回可用于取消订阅的句柄。对于每个收到的消息,处理程序可以指定有效负载应该反序列化的目标对象类型:
为了使STOMP心跳配置有 和任选定制心跳间隔10秒,其将导致一个心跳写入不活动待发送和10秒不活动读取其关闭连接。
STOMP协议还支持收据,其中客户端必须添加“收据”标头,服务器在处理发送或订阅后用RECEIPT帧响应。为了支持这个提议 导致要在以后每发送添加或订阅了“回执”标头。或者,您也可以手动添加“收据”标题。发送和订阅都返回一个实例 ,可用于注册接收成功和失败回调。对于此功能,客户端必须配置a 和收据到期前的时间(默认为15秒)。
请注意,除了处理消息的异常回调以及传输级错误(包括)之外,它本身还允许它处理ERROR帧。
4.4.16。WebSocket范围
每个WebSocket会话都有一个属性映射。映射作为标头附加到入站客户端消息,并且可以从控制器方法访问,例如:
也可以在作用域中声明一个Spring管理的bean 。WebSocket范围的bean可以注入控制器和“clientInboundChannel”上注册的任何通道拦截器。这些通常是单身,比任何单独的WebSocket会话都更长寿。因此,您需要为WebSocket范围的bean使用范围代理模式:
与任何自定义作用域一样,Spring 在第一次从控制器访问时初始化一个新实例,并将该实例存储在WebSocket会话属性中。随后返回相同的实例,直到会话结束。WebSocket范围的bean将调用所有Spring生命周期方法,如上面的示例所示。
4.4.17。性能
在性能方面没有银弹。许多因素可能会影响它,包括消息的大小,数量,应用程序方法是否执行需要阻止的工作,以及外部因素,如网络速度等。本部分的目标是提供可用配置选项的概述以及有关如何推理缩放的一些想法。
在消息传递应用程序中,消息通过用于由线程池支持的异步执行的通道传递。配置此类应用程序需要充分了解通道和消息流。因此,建议查看。
显而易见的起点是配置支持和的线程池 。默认情况下,两者都配置为可用处理器数量的两倍。
如果注释方法中的消息处理主要是CPU绑定的,则应该保持接近处理器数量的线程数。如果他们所做的工作更多是IO绑定并且需要阻塞或等待数据库或其他外部系统,则需要增加线程池大小。
另一方面,它是关于向WebSocket客户端发送消息。如果客户端位于快速网络上,则线程数应保持接近可用处理器的数量。如果它们很慢或带宽较低,则消耗消息所需的时间会更长,并给线程池带来负担。因此,增加线程池大小是必要的。
虽然“clientInboundChannel”的工作负载可以预测 - 毕竟它基于应用程序的工作 - 如何配置“clientOutboundChannel”更难,因为它基于应用程序无法控制的因素。因此,有两个与发送消息相关的附加属性。那些是 和。这些用于配置允许发送多长时间以及在向客户端发送消息时可以缓冲多少数据。
一般的想法是,在任何给定时间,只有一个线程可用于发送给客户端。同时,所有其他消息都会被缓冲,您可以使用这些属性来决定允许发送消息的时间长度以及可以在平均时间内缓冲多少数据。有关重要的其他详细信息,请查看此配置的XML架构的Javadoc和文档。
以下是示例配置:
上面显示的WebSocket传输配置还可用于配置传入STOMP消息的最大允许大小。虽然理论上WebSocket消息的大小几乎是无限的,但实际上WebSocket服务器会施加限制 - 例如,Tomcat上的8K和Jetty上的64K。因此,诸如JavaScript 等STOMP 在16K边界处拆分较大的STOMP消息,并将它们作为多个WebSocket消息发送,因此需要服务器缓冲和重新组装。
Spring的STOMP over WebSocket支持这样做,因此应用程序可以配置STOMP消息的最大大小,而不管WebSocket服务器特定的消息大小。请记住,必要时将自动调整WebSocket消息大小,以确保它们至少可以携带16K WebSocket消息。
以下是示例配置:
关于扩展的一个重点是使用多个应用程序实例。目前,使用简单代理无法做到这一点。但是,当使用RabbitMQ等功能齐全的代理时,每个应用程序实例都会连接到代理,并且从一个应用程序实例广播的消息可以通过代理广播到通过任何其他应用程序实例连接的WebSocket客户端。
4.4.18。监控
使用或关键基础架构组件时,会自动收集统计信息和计数器,以便深入了解应用程序的内部状态。该配置还声明了一个类型的bean,它在一个地方收集所有可用信息,默认情况下每30分钟将其记录一次。这个bean可以通过Spring导出到JMX ,以便在运行时查看,例如通过JDK 。以下是可用信息的摘要。
客户端WebSocket会话
当前
表示当前有多少客户端会话,其中包括WebSocket与HTTP流和轮询SockJS会话进一步细分的计数。
总
表示已建立的会话总数。
异常关闭
连接失败
这些会话已经建立但在60秒内没有收到任何消息后关闭。这通常表示代理或网络问题。
超出发送限制
超过配置的发送超时或缓慢客户端可能发生的发送缓冲区限制后会话关闭(请参阅上一节)。
运输错误
在传输错误(例如无法读取或写入WebSocket连接或HTTP请求/响应)之后会话关闭。
STOMP框架
处理的CONNECT,CONNECTED和DISCONNECT帧总数,表示STOMP级别连接的客户端数量。请注意,当会话异常关闭或客户端关闭而不发送DISCONNECT帧时,DISCONNECT计数可能会更低。
STOMP经纪人接力
TCP连接
表示代表客户端WebSocket会话建立多少个TCP连接到代理。这应该等于客户端WebSocket会话的数量+ 1个用于从应用程序内发送消息的额外共享“系统”连接。
STOMP框架
代表客户端转发到代理或从代理接收的CONNECT,CONNECTED和DISCONNECT帧的总数。请注意,无论客户端WebSocket会话如何关闭,都会将DISCONNECT帧发送到代理。因此,较低的DISCONNECT帧计数表示代理主动关闭连接,可能是因为没有及时到达的心跳,无效的输入帧或其他。
客户端入站通道
来自线程池的统计信息支持“clientInboundChannel”,提供对传入消息处理的运行状况的深入了解。在此排队的任务表明应用程序可能太慢而无法处理消息。如果有I / O绑定任务(例如,慢速数据库查询,对第三方REST API的HTTP请求等),请考虑增加线程池大小。
客户出站频道
支持“clientOutboundChannel”的线程池中的统计信息,提供对客户端广播消息运行状况的深入了解。在此排队的任务表明客户端消耗消息的速度太慢。解决此问题的一种方法是增加线程池大小以适应预期的并发慢客户端数量。另一种选择是减少发送超时和发送缓冲区大小限制(参见上一节)。
SockJS任务计划程序
来自SockJS任务调度程序的线程池的统计信息,用于发送心跳。请注意,在STOMP级别协商心跳时,将禁用SockJS心跳。
4.4.19。测试
使用Spring的STOMP over WebSocket支持测试应用程序有两种主要方法。第一种是编写服务器端测试来验证控制器的功能及其带注释的消息处理方法。第二种是编写涉及运行客户端和服务器的完整端到端测试。
这两种方法并不相互排斥。相反,每个人都在整体测试策略中占有一席之地。服务器端测试更集中,更易于编写和维护。另一方面,端到端集成测试更完整,测试更多,但它们也更多地参与编写和维护。
最简单的服务器端测试形式是编写控制器单元测试。然而,由于控制器的大部分功能取决于其注释,因此这没有用。纯单元测试根本无法测试。
理想情况下,测试中的控制器应该在运行时调用,就像测试使用Spring MVC测试框架处理HTTP请求的控制器的方法一样。即没有运行Servlet容器,而是依赖Spring Framework来调用带注释的控制器。就像Spring MVC Test一样,有两种可能的替代方案,使用“基于上下文”或“独立”设置:
示例应用程序的中演示了这两种设置方案 。
第二种方法是创建端到端集成测试。为此,您需要以嵌入模式运行WebSocket服务器,并将其作为WebSocket客户端连接到它,发送包含STOMP帧的WebSocket消息。 示例应用程序的还演示了使用Tomcat作为嵌入式WebSocket服务器和用于测试目的的简单STOMP客户端的这种方法。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/192731.html