Spring Websocket 中文文档
Spring Websocket 中文文档
GuoMengYue 发表于1年前
Spring Websocket 中文文档
  • 发表于 1年前
  • 阅读 880
  • 收藏 9
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

26. WebSocket支持

这部分参考文档涵盖了Spring Framework对Web应用程序中WebSocket风格的消息传递的支持,包括使用STOMP作为应用程序级WebSocket子协议。

第26.1节“简介”  建立了一个思维框架,思考WebSocket,涵盖应用挑战,设计注意事项,以及什么时候适合。

第26.2节“WebSocket API”  评估了服务器端的Spring WebSocket API,而 第26.3节“SockJS后备选项”  解释了SockJS协议并展示了如何配置和使用它。

第26.4.1节“STOMP概述”  介绍了STOMP消息传递协议。 第26.4.2节“在WebSocket上启用STOMP”  演示了如何在Spring中配置STOMP支持。 第26.4.4节“注释消息处理”  以及以下部分说明如何编写注释消息处理方法,发送消息,选择消息代理选项以及使用特殊的“用户”目标。最后, 第26.4.18节“测试注释控制器方法”  列出了测试STOMP / WebSocket应用程序的三种方法。

26.1介绍

WebSocket协议  RFC 6455  为Web应用程序定义了一个重要的新功能:客户端和服务器之间的全双工,双向通信。这是一个令人兴奋的新功能,具有悠久的技术历史,使Web更具互动性,包括Java Applets,XMLHttpRequest,Adobe Flash,ActiveXObject,各种Comet技术,服务器发送事件等。

对WebSocket协议的适当介绍超出了本文档的范围。然而,最低限度地,重要的是要理解HTTP仅用于初始握手,其依赖于内置于HTTP中的机制以请求协议升级(或者在这种情况下是协议交换机),服务器可以使用HTTP状态101响应该协议升级(切换协议)如果它同意。假设握手成功,HTTP升级请求下的TCP套接字保持打开,客户端和服务器都可以使用它来相互发送消息。

Spring Framework 4包括一个新的  spring-websocket  模块,具有全面的WebSocket支持。它与Java WebSocket API标准(JSR-356)兼容,并且还提供了附加的增值,如介绍的其余部分所述。

26.1.1 WebSocket后备选项

采用的一个重要挑战是在一些浏览器中缺乏对WebSocket的支持。值得注意的是,支持WebSocket的第一个Internet Explorer版本是版本10(有关 浏览器版本的支持,请参阅  http://caniuse.com/websockets)。此外,一些限制性代理可以以这样的方式配置,其或者阻止尝试进行HTTP升级,或者在一段时间之后断开连接,因为它已经打开太久。有关这个主题的良好概述,来自Peter Lubbers的InfoQ文章  “如何HTML5 Web套接字与代理服务器交互”

因此,为了在今天构建WebSocket应用程序,需要回退选项,以便在必要时模拟WebSocket API。Spring框架基于SockJS协议提供了这样的透明回退选项  。这些选项可以通过配置启用,并且不需要修改应用程序。

26.1.2消息架构

除了短期到中期采用的挑战,使用WebSocket提出了重要的早期认识的重要设计考虑,特别是与我们今天构建Web应用程序的知识相反。

今天,REST是用于构建Web应用程序的广泛接受,理解和支持的体系结构。它是一个依赖于拥有许多URL(名词),少数HTTP方法(动词)和其他原则(如使用超媒体(链接),保持无状态等)的架构。

相反,WebSocket应用程序可以仅使用单个URL作为初始HTTP握手。所有消息此后共享并在同一TCP连接上流动。这指向一个完全不同的,异步的,事件驱动的消息传递架构。一个更接近传统的消息传递应用程序(例如JMS,AMQP)。

Spring Framework 4包括一个新的  spring-messaging  模块,它具有来自Spring Integration  项目的关键抽象,  例如  Message ,  MessageChannel ,  MessageHandler 和其他可以作为这种消息传递架构的基础。该模块还包括一组用于将消息映射到方法的注释,类似于基于Spring MVC注释的编程模型。

26.1.3 WebSocket中的子协议支持

WebSocket意味着  消息传递架构,  但不强制要求使用任何特定的  消息协议。它是一个非常薄的TCP层,将字节流转换为消息流(文本或二进制),而不是更多。它是由应用程序来解释消息的含义。

与HTTP(这是一种应用程序级协议)不同,在WebSocket协议中,框架或容器的入站消息中没有足够的信息来知道如何路由或处理它。因此,WebSocket可以说是太低的水平,任何东西,但一个非常简单的应用程序。它可以做到,但它可能会导致在顶部创建一个框架。这与当今大多数Web应用程序如何使用Web框架而不是仅使用Servlet API编写的内容类似。

因此,WebSocket RFC定义了协议的使用  。在握手期间,客户端和服务器可以使用报头  Sec-WebSocket-Protocol  来商定要使用的子协议,即更高的应用级协议。不需要使用子协议,但即使不使用,应用仍然需要选择客户端和服务器都能理解的消息格式。该格式可以是定制的,框架特定的或标准消息传递协议。

Spring框架提供了对使用STOMP的支持,  STOMP  是一种简单的消息传递协议,最初创建用于脚本语言,并使用受HTTP启发的框架。STOMP得到广泛支持,非常适合在WebSocket和Web上使用。

26.1.4我应该使用WebSocket吗?

考虑到围绕使用WebSocket的所有设计考虑,有理由问“使用什么时候适合使用?

最适合WebSocket是在Web应用程序中,客户端和服务器需要以高频率和低延迟交换事件。主要候选人包括但不限于金融,游戏,协作和其他应用程序。这样的应用对时间延迟都非常敏感,并且还需要以高频交换各种各样的消息。

对于其他应用程序类型,但是,可能不是这样。例如,当新闻或社交馈送在其可用时显示突发新闻可能完全可以与简单轮询每几分钟一次。这里延迟很重要,但如果新闻需要几分钟才能出现,这是可以接受的。

即使在延迟很关键的情况下,如果消息量相对较低(例如,监视网络故障),则长轮询的使用   应该被认为是相对简单的替代方案,其可靠地工作并且在效率方面是可比的(再次假定卷的消息相对较低)。

它是低延迟和高频消息的组合,可以使WebSocket协议的使用至关重要。即使在这样的应用程序中,选择仍然是所有客户端 - 服务器通信是否应该通过WebSocket消息,而不是使用HTTP和REST。答案会因应用而异; 然而,很可能某些功能可能通过WebSocket和REST API公开,以便为客户提供替代方案。此外,REST API调用可能需要向通过WebSocket连接的感兴趣的客户端广播消息。

Spring框架允许  @Controller  和  @RestController  类同时拥有HTTP请求处理和WebSocket消息处理方法。此外,Spring MVC请求处理方法或用于此的任何应用方法可以容易地向所有感兴趣的WebSocket客户端或向特定用户广播消息。

26.2 WebSocket API

Spring框架提供了一个WebSocket API,用于适应各种WebSocket引擎。目前列表包括WebSocket运行时,如Tomcat 7.0.47+,Jetty 9.1+,GlassFish 4.1+,WebLogic 12.1.3+和Undertow 1.0+(和WildFly 8.0+)。当更多的WebSocket运行时可用时,可以添加额外的支持。

[注意]

 

正如在介绍中所  解释的,直接使用WebSocket API对于应用程序来说太低了- 直到假定消息的格式,框架才能解释消息或通过注释路由它们。这就是为什么应用程序应该考虑使用子协议和Spring的  STOMP over WebSocket  支持。  

当使用更高级别的协议时,WebSocket API的细节变得不那么相关,就像在使用HTTP时TCP通信的细节不暴露给应用程序。尽管如此,本节将详细介绍如何直接使用WebSocket。

 

26.2.1创建和配置WebSocketHandler

创建WebSocket服务器就像实现WebSocketHandler 一样简单,   或者更有可能扩展  TextWebSocketHandler  或  BinaryWebSocketHandler :

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.TextMessage;

 

public class MyHandler extends TextWebSocketHandler {

 

    @覆盖

    public void handleTextMessage(WebSocketSession session,TextMessage message){

        // ...

    }}

 

}}

有专门的WebSocket Java-config和XML命名空间支持将上述WebSocket处理程序映射到特定的URL:

import org.springframework.web.socket.config.annotation.EnableWebSocket;

import org.springframework.web.socket.config.annotation.WebSocketConfigurer;

import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

 

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        registry.addHandler(myHandler(),“/ myHandler” );

    }}

 

    @豆

    public WebSocketHandler myHandler(){

        return new MyHandler();

    }}

 

}}

XML配置等效:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:handlers>

        <websocket:mapping path = “/ myHandler” handler = “myHandler” />

    </ websocket:handlers>

 

    <bean id = “myHandler” class = “org.springframework.samples.MyHandler” />

 

</ beans>

上面是用于Spring MVC应用程序,应该包含在DispatcherServlet的  配置中。但是,Spring的WebSocket支持不依赖于Spring MVC。在WebSocketHttpRequestHandler  的帮助下,将  WebSocketHandler 集成到其他HTTP服务环境相对  简单

26.2.2自定义WebSocket握手

自定义初始HTTP WebSocket握手请求的最简单的方法是通过  HandshakeInterceptor ,它暴露“之前”和“之后”的握手方法。这样的拦截器可以用于排除握手或使任何属性可用于  WebSocketSession 。例如,有一个内置拦截器用于将HTTP会话属性传递到WebSocket会话:

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        registry.addHandler(new MyHandler(),“/ myHandler” )

            .addInterceptors(new HttpSessionHandshakeInterceptor());

    }}

 

}}

和XML配置等价:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:handlers>

        <websocket:mapping path = “/ myHandler” handler = “myHandler” />

        <websocket:handshake-interceptors>

            <bean class = “org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor” />

        </ websocket:handshake-interceptors>

    </ websocket:handlers>

 

    <bean id = “myHandler” class = “org.springframework.samples.MyHandler” />

 

</ beans>

更先进的方法是延长  DefaultHandshakeHandler  执行WebSocket的握手的步骤,包括验证所述客户端原点,谈判的子协议,以及其他。如果应用程序需要配置自定义RequestUpgradeStrategy  以适应尚不支持的WebSocket服务器引擎和版本,则还需要使用此选项  ( 有关此主题的更多信息,另请参见  第26.2.4节“部署注意事项”))。Java-config和XML命名空间都可以配置一个自定义的  HandshakeHandler 。

26.2.3 WebSocketHandler装饰

Spring提供了一个  WebSocketHandlerDecorator  ,可以用来装饰一个基类  WebSocketHandler  额外的行为。在使用WebSocket Java-config或XML命名空间时,默认提供并添加了日志和异常处理实现。该  ExceptionWebSocketHandlerDecorator  捕获来自任何WebSocketHandler方法产生的所有捕获的异常,并关闭与状态的WebSocket会议  1011  ,表明服务器错误。

26.2.4部署注意事项

Spring WebSocket API很容易集成到Spring MVC应用程序中,  DispatcherServlet  既支持HTTP WebSocket握手,也支持其他HTTP请求。通过调用WebSocketHttpRequestHandler ,也很容易集成到其他HTTP处理场景中  。这是方便和容易理解。但是,对于JSR-356运行时,有特殊的考虑。

Java WebSocket API(JSR-356)提供了两种部署机制。第一个涉及在启动时的Servlet容器类路径扫描(Servlet 3特性)另一个是在Servlet容器初始化时使用的注册API。这两种机制都不可能为所有HTTP处理使用单个“前端控制器” - 包括WebSocket握手和所有其他HTTP请求,如Spring MVC的  DispatcherServlet 。

这是Spring的WebSocket支持通过提供一个特定于服务器的  RequestUpgradeStrategy, 即使在JSR-356运行时运行的JSR-356的重大限制。

[注意]

 

已经创建了在Java WebSocket API中克服上述限制的请求,并且可以在  WEBSOCKET_SPEC-211中执行。还要注意,Tomcat和Jetty已经提供了原生的API替代方法,使得容易克服这个限制。我们希望更多的服务器将遵循他们的例子,无论它在Java WebSocket API中何时被解决。

 

第二个考虑是具有JSR-356支持的Servlet容器预期执行  ServletContainerInitializer  (SCI)扫描,这可以减慢应用程序启动,在某些情况下,显着地。如果升级到Servlet容器版本使用JSR-356支撑后观察到显著的影响,应该尽可能以选择性地启用或通过使用的停用网络片段(和SCI扫描)  <绝对排序/>  在元件  幅.xml :

<web-app xmlns = “http://java.sun.com/xml/ns/javaee”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xsi:schemaLocation = “

        http://java.sun.com/xml/ns/javaee

        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd“

    version = “3.0” >

 

    <absolute-ordering />

 

</ web-app>

然后,您可以按名称选择性启用Web片段,例如Spring自己的  SpringServletContainerInitializer  ,如果需要,为Servlet 3 Java初始化API提供支持:

<web-app xmlns = “http://java.sun.com/xml/ns/javaee”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xsi:schemaLocation = “

        http://java.sun.com/xml/ns/javaee

        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd“

    version = “3.0” >

 

    <absolute-ordering>

        <name> spring_web </ name>

    </ absolute-ordering>

 

</ web-app>

26.2.5配置WebSocket引擎

每个底层WebSocket引擎都提供了控制运行时特性的配置属性,例如消息缓冲区大小,空闲超时等。

对于Tomcat,WildFly和GlassFish  ,向您的WebSocket Java配置添加一个  ServletServerContainerFactoryBean :

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @豆

    public ServletServerContainerFactoryBean createWebSocketContainer(){

        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();

        container.setMaxTextMessageBufferSize(8192);

        container.setMaxBinaryMessageBufferSize(8192);

        返回容器;

    }}

 

}}

或WebSocket XML命名空间:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <bean class = “org.springframework ... ServletServerContainerFactoryBean” >

        <property name = “maxTextMessageBufferSize” value = “8192” />

        <property name = “maxBinaryMessageBufferSize” value = “8192” />

    </ bean>

 

</ beans>

[注意]

 

对于客户端WebSocket配置,应该使用  WebSocketContainerFactoryBean  (XML)或  ContainerProvider.getWebSocketContainer() (Java config)。

 

对于Jetty,你需要提供一个预先配置的Jetty  WebSocketServerFactory  ,并 通过你的WebSocket Java配置插入Spring的  DefaultHandshakeHandler :

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        registry.addHandler(echoWebSocketHandler(),

            “/ echo” ).setHandshakeHandler(handshakeHandler());

    }}

 

    @豆

    public DefaultHandshakeHandler handshakeHandler(){

 

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);

        policy.setInputBufferSize(8192);

        policy.setIdleTimeout(600000);

 

        return new DefaultHandshakeHandler(

                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));

    }}

 

}}

或WebSocket XML命名空间:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:handlers>

        <websocket:mapping path = “/ echo” handler = “echoHandler” />

        <websocket:handshake-handler ref = “handshakeHandler” />

    </ websocket:handlers>

 

    <bean id = “handshakeHandler” class = “org.springframework ... DefaultHandshakeHandler” >

        <constructor-arg ref = “upgradeStrategy” />

    </ bean>

 

    <bean id = “upgradeStrategy” class = “org.springframework ... JettyRequestUpgradeStrategy” >

        <constructor-arg ref = “serverFactory” />

    </ bean>

 

    <bean id = “serverFactory” class = “org.eclipse.jetty ... WebSocketServerFactory” >

        <constructor-arg>

            <bean class = “org.eclipse.jetty ... WebSocketPolicy” >

                <constructor-arg value = “SERVER” />

                <property name = “inputBufferSize” value = “8092” />

                <property name = “idleTimeout” value = “600000” />

            </ bean>

        </ constructor-arg>

    </ bean>

 

</ beans>

26.2.6配置允许的源

从Spring Framework 4.1.5开始,WebSocket和SockJS的默认行为是只接受  相同的源  请求。也可以允许  所有  或指定的原点列表。此检查主要是为浏览器客户端设计的。没有什么阻止其他类型的客户端修改  Origin  头值( 有关详细信息,请参阅  RFC 6454:Web Origin Concept)。

3种可能的行为是:

  • 仅允许相同的原始请求(默认):在此模式下,当SockJS启用时,Iframe HTTP响应头  X-Frame-Options  设置为  SAMEORIGIN ,并且禁用JSONP传输,因为它不允许检查请求的来源。因此,当启用此模式时,不支持IE6和IE7。
  • 允许指定的起点列表:每个提供的  允许起点  必须以  http://  或  https://开头。在此模式下,当SockJS启用时,基于IFrame和JSONP的传输都被禁用。因此,当启用此模式时,不支持IE6至IE9。
  • 允许所有原点:要启用此模式,应提供  *  作为允许的原始值。在此模式下,所有传输都可用。

WebSocket和SockJS允许的起点可以配置如下所示:

import org.springframework.web.socket.config.annotation.EnableWebSocket;

import org.springframework.web.socket.config.annotation.WebSocketConfigurer;

import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

 

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        registry.addHandler(myHandler(),“/ myHandler” ).setAllowedOrigins(“http://mydomain.com” );

    }}

 

    @豆

    public WebSocketHandler myHandler(){

        return new MyHandler();

    }}

 

}}

XML配置等效:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:handlers allowed-origins = “http://mydomain.com” >

        <websocket:mapping path = “/ myHandler” handler = “myHandler” />

    </ websocket:handlers>

 

    <bean id = “myHandler” class = “org.springframework.samples.MyHandler” />

 

</ beans>

26.3 SockJS回退选项

如介绍中所  解释的,WebSocket在所有浏览器中都不受支持,可能会被限制性网络代理排除。这就是为什么Spring提供了基于SockJS协议  (版本0.3.3)尽可能接近地模拟WebSocket API的后备选项  。

26.3.1 SockJS概述

SockJS的目标是让应用程序使用WebSocket API,但在运行时需要时可以退回到非WebSocket替代项,即无需更改应用程序代码。

SockJS包括:

  • 该  SockJS协议  中的可执行文件的形式定义的  叙述测试
  • 该  SockJS JavaScript客户端  -在浏览器中使用客户端库。
  • SockJS服务器实现,包括Spring框架  spring-websocket  模块中的一个。
  • 从4.1  弹簧websocket  还提供了一个SockJS Java客户端。

SockJS专为在浏览器中使用而设计。它使用各种技术支持各种浏览器版本。有关SockJS传输类型和浏览器的完整列表,请参阅  SockJS客户端  页面。传输分为三大类:WebSocket,HTTP流传输和HTTP长轮询。有关这些类别的概述,请参阅  此博客文章

SockJS客户端通过发送  “GET / info”  来从服务器获取基本信息。之后,它必须决定使用什么传输。如果可能,使用WebSocket。如果不是,在大多数浏览器中至少有一个HTTP流选项,如果没有,则使用HTTP(长)轮询。

所有传输请求具有以下URL结构:

http:// host:port / myApp / myEndpoint / {server-id} / {session-id} / {transport}

  • {server-id}  - 用于在集群中路由请求,但不以其他方式使用。
  • {session-id}  - 关联属于SockJS会话的HTTP请求。
  • {transport}  - 表示传输类型,例如“websocket”,“xhr-streaming”等。

WebSocket传输仅需要一个HTTP请求来执行WebSocket握手。此后的所有消息都在该套接字上交换。

HTTP传输需要更多请求。例如,Ajax / XHR流传输依赖于一个长期运行的服务器到客户端消息的请求和用于客户端到服务器消息的附加HTTP POST请求。长轮询类似,除了它在每个服务器到客户端发送之后结束当前请求。

SockJS增加了最小的消息框架。例如,服务器最初发送字母o(“开放”帧),如果没有消息流,则消息以[“message1”,“message2”](JSON编码的数组),字母h默认为25秒,以及字母c(“关闭”框)来关闭会话。

要了解详情,请在浏览器中运行示例并观察HTTP请求。SockJS客户端允许修复传输列表,因此可以一次查看每个传输。SockJS客户端还提供了一个调试标志,可在浏览器控制台中启用有用的消息。在服务器端启用  org.springframework.web.socket的TRACE  日志记录  。有关更多详细信息,请参阅SockJS协议  叙述的测试

26.3.2启用SockJS

SockJS很容易通过Java配置启用:

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){

        registry.addHandler(myHandler(),“/ myHandler” ).withSockJS();

    }}

 

    @豆

    public WebSocketHandler myHandler(){

        return new MyHandler();

    }}

 

}}

和XML配置等价:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:handlers>

        <websocket:mapping path = “/ myHandler” handler = “myHandler” />

        <websocket:sockjs />

    </ websocket:handlers>

 

    <bean id = “myHandler” class = “org.springframework.samples.MyHandler” />

 

</ beans>

上面是用于Spring MVC应用程序,应该包含在DispatcherServlet的  配置中。但是,Spring的WebSocket和SockJS支持不依赖于Spring MVC。这是比较简单的融入的帮助下其他HTTP服务环境  SockJsHttpRequestHandler

在浏览器端,应用程序可以使用 模拟W3C WebSocket API 的  sockjs-client(版本1.0.x),并与服务器通信,以根据运行的浏览器选择最佳传输选项。查看  sockjs-client  页面和浏览器支持的传输类型列表。客户端还提供了几个配置选项,例如,指定要包括哪些传输。

26.3.3 IE 8,9中的HTTP流:Ajax / XHR对IFrame

Internet Explorer 8和9将保持通用一段时间。他们是SockJS的一个关键原因。本节包含在这些浏览器中运行的重要注意事项。

SockJS客户端通过Microsoft的XDomainRequest支持在IE 8和9中的Ajax / XHR  。这项工作跨域,但不支持发送Cookie。Cookie对于Java应用程序通常是必需的。然而,由于SockJS客户端可以与许多服务器类型(不仅仅是Java类型)一起使用,它需要知道cookie是否重要。如果是这样,SockJS客户端优选Ajax / XHR用于流传输,或者否则它依赖于基于iframe的技术。

 来自SockJS客户端的第一个  “/ info” 请求是对可以影响客户端对传输的选择的信息的请求。其中一个细节是服务器应用程序是否依赖于Cookie,例如用于验证目的或具有粘性会话的群集。Spring的SockJS支持包括一个名为sessionCookieNeeded 的属性  。它是默认启用的,因为大多数Java应用程序依赖于  JSESSIONID  cookie。如果您的应用程序不需要它,您可以关闭此选项,SockJS客户端应 在IE 8和9中选择  xdr流。

如果你使用基于iframe的传输,并且在任何情况下,很好地知道,浏览器可以通过设置HTTP响应头X-Frame-Options  为  DENY ,  SAMEORIGIN 来指示阻止在给定页面上使用IFrame  ,或  ALLOW-FROM <origin> 。这用于防止  点击劫持

[注意]

 

Spring Security 3.2+提供了 对每个响应设置X-Frame-Options的支持  。默认情况下,Spring Security Java配置将其设置为  DENY 。在3.2中,Spring Security XML命名空间默认不设置头部,但是可以配置为这样做,并且将来可以默认设置它。

参见  7.1节。 有关如何配置X-Frame-Options 标题的设置的详细信息,请参阅Spring Security文档的  “默认安全标题”。您还可以查看或观看  SEC-2501  了解更多背景信息。

 

如果您的应用程序添加了  X-Frame-Options  响应头(因为它应该!)并依赖于基于iframe的传输,您将需要设置标头值为SAMEORIGIN  或  ALLOW-FROM <origin> 。与此同时,Spring SockJS支持还需要知道SockJS客户端的位置,因为它是从iframe加载的。默认情况下,iframe设置为从CDN位置下载SockJS客户端。最好将此选项配置为与应用程序相同的来源的URL。

在Java配置中,可以如下所示完成。XML命名空间通过<websocket:sockjs>  元素提供了类似的选项  :

@组态

@EnableWebSocket

public class WebSocketConfig implements WebSocketConfigurer {

 

    @覆盖

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint(“/ portfolio” ).withSockJS()

                .setClientLibraryUrl(“http:// localhost:8080 / myapp / js / sockjs-client.js” );

    }}

 

    // ...

 

}}

[注意]

 

在最初的发展,也使SockJS客户  DEVEL  防止浏览器缓存SockJS请求(如的iframe),否则将被缓存模式。有关如何启用它的详细信息,请参阅  SockJS客户端  页面。

 

26.3.4心跳消息

SockJS协议要求服务器发送心跳消息以阻止代理断定连接挂起。Spring SockJS配置有一个名为heartbeatTime 的属性   ,可用于自定义频率。默认情况下,在25秒后发送心跳,假设在该连接上没有发送其他消息。这25秒的值符合以下  IETF  对公共互联网应用的建议。

[注意]

 

当在WebSocket / SockJS上使用STOMP时,如果STOMP客户端和服务器协商要交换的心跳,则会禁用SockJS心跳。

 

Spring SockJS支持还允许将  TaskScheduler配置 为用于调度心跳任务。任务调度程序由具有基于可用处理器数量的默认设置的线程池支持。应用程序应考虑根据其特定需要自定义设置。

26.3.5 Servlet 3异步请求

HTTP流和HTTP长轮询SockJS传输需要连接保持打开比通常更长。有关这些技术的概述,请参阅  此博客文章

在Servlet容器中,这是通过Servlet 3异步支持完成的,它允许退出Servlet容器线程处理请求并继续写入来自另一个线程的响应。

一个具体的问题是,Servlet API不为已经离开的客户端提供通知,请参见  SERVLET_SPEC-44。但是,Servlet容器在后续尝试写入响应时引发异常。由于Spring的SockJS服务支持服务器发送的心跳(默认情况下每25秒),这意味着通常在该时间段内检测到客户端断开连接,如果消息发送频率更高,则会更早地检测到。

[注意]

 

因此,网络IO故障可能只是因为客户端已断开连接,这可能会用不必要的堆栈跟踪填充日志。弹簧使得鉴定这类网络故障代表客户端断开(具体到每个服务器),并使用专用日志类别登录最小消息尽力而为  DISCONNECTED_CLIENT_LOG_CATEGORY  限定  AbstractSockJsSession 。如果需要查看堆栈跟踪,请将该日志类别设置为TRACE。

 

26.3.6 SockJS的CORS头

如果允许跨源请求(请参见  第26.2.6节“配置允许的源”),SockJS协议在XHR流式传输和轮询传输中使用CORS进行跨域支持。因此,CORS头被自动添加,除非检测到响应中存在CORS头。因此,如果应用程序已经配置为提供CORS支持,例如通过Servlet过滤器,Spring的SockJsService将跳过此部分。

还可以通过 Spring SockJsService 中的suppressCors 属性禁用这些CORS头  的添加。

以下是SockJS期望的头和值的列表:

  • “Access-Control-Allow-Origin”  - 从“Origin”请求头的值初始化。
  • “Access-Control-Allow-Credentials”  - 始终设置为  true 。
  • “访问控制请求报头”  - 从等效请求报头的值初始化。
  • “Access-Control-Allow-Methods”  - 传输支持的HTTP方法(请参阅  TransportType  枚举)。
  • “Access-Control-Max-Age”  - 设置为31536000(1年)。

有关确切实现,请参阅  AbstractSockJsService  中的  addCorsHeaders  以及 源代码中的  TransportType 枚举。

或者,如果CORS配置允许它考虑排除具有SockJS端点前缀的URL,从而让Spring的  SockJsService  处理它。

26.3.7 SockJS客户端

提供SockJS Java客户端以便在不使用浏览器的情况下连接到远程SockJS端点。当需要通过公共网络在两个服务器之间进行双向通信时,这是特别有用的,即,其中网络代理可以阻止使用WebSocket协议。SockJS Java客户端对于测试也非常有用,例如模拟大量的并发用户。

SockJS Java客户端支持“websocket”,“xhr-streaming”和“xhr-polling”传输。其余的只有在浏览器中使用才有意义。

该  WebSocketTransport  可以配置为使用:

  •  JSR-356运行时中的StandardWebSocketClient
  • JettyWebSocketClient  使用Jetty 9+本机WebSocket API
  • 任何实现Spring的  WebSocketClient

一个  XhrTransport  定义支持因为从客户端的角度来看,没有比在用于连接到所述服务器的URL等区别两者“XHR-流”和“XHR轮询”。目前有两种实现:

  • RestTemplateXhrTransport  使用Spring的  RestTemplate  来处理HTTP请求。
  • JettyXhrTransport  使用Jetty的  HttpClient  进行HTTP请求。

下面的示例显示如何创建SockJS客户端并连接到SockJS端点:

List <Transport> transports = new ArrayList <>(2);

transports.add(new WebSocketTransport(new StandardWebSocketClient()));

transports.add(new RestTemplateXhrTransport());

 

SockJsClient sockJsClient = new SockJsClient(transports);

sockJsClient.doHandshake(new MyWebSocketHandler(),“ws://example.com:8080 / sockjs” );

[注意]

 

SockJS对消息使用JSON格式的数组。默认情况下使用Jackson 2,并且需要在类路径上。另外,您可以配置的自定义实现  SockJsMessageCodec  并在配置  SockJsClient 。

 

要使用SockJsClient来模拟大量的并发用户,您需要配置底层HTTP客户端(对于XHR传输)以允许足够数量的连接和线程。例如与Jetty:

HttpClient jettyHttpClient = new HttpClient();

jettyHttpClient.setMaxConnectionsPerDestination(1000);

jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

还请考虑自定义这些服务器端SockJS相关属性(有关详细信息,请参阅Javadoc):

@组态

public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

 

    @覆盖

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint(“/ sockjs” ).withSockJS()

            .setStreamBytesLimit(512 * 1024)

            .setHttpMessageCacheSize(1000)

            .setDisconnectDelay(30 * 1000);

    }}

 

    // ...

 

}}

26.4通过WebSocket消息传递架构的STOMP

WebSocket协议定义了两种类型的消息,文本和二进制,但它们的内容未定义。期望客户端和服务器可以同意使用子协议(即,更高级协议)来定义消息语义。虽然使用WebSocket的子协议是完全可选的,但客户端和服务器都需要同意某种协议来帮助解释消息。

26.4.1 STOMP概述

STOMP  是一种简单的面向文本的消息传递协议,最初为脚本语言(如Ruby,Python和Perl)创建,以连接到企业消息代理。它被设计为寻址常用的消息模式的子集。STOMP可以用于任何可靠的双向流网络协议,如TCP和WebSocket。虽然STOMP是面向文本的协议,但消息的有效载荷可以是文本或二进制。

STOMP是基于帧的协议,其帧是基于HTTP建模的。STOMP框架的结构:

命令

header1:value1

header2:value2

 

身体^ @

客户端可以使用SEND或SUBSCRIBE命令发送或订阅消息以及描述消息是什么以及应该接收消息的“目标”头。这使得能够使用简单的发布 - 订阅机制,其可以用于通过代理将消息发送到其他连接的客户机或者向服务器发送消息以请求执行某些工作。

当使用Spring的STOMP支持时,Spring WebSocket应用程序充当客户端的STOMP代理。消息路由到@Controller  消息处理方法或路由到  简单的内存代理,它跟踪订阅并向订阅用户广播消息。您还可以配置Spring以使用专用的STOMP代理(例如RabbitMQ,ActiveMQ等)来实际广播消息。在这种情况下,Spring维护到代理的TCP连接,中继消息到它,并且还将消息从它传递到连接的WebSocket客户端。因此Spring Web应用程序可以依赖于统一的基于HTTP的安全性,通用验证和熟悉的编程模型消息处理工作。

这里是订阅接收股票报价的客户端的示例,服务器可以周期性地发送,例如经由调度的任务通过SimpMessagingTemplate  向代理发送消息  :

订阅

id:sub-1

目的地:/topic/price.stock.*

 

^ @

下面是客户端发送交易请求的示例,服务器可以通过@MessageMapping  方法处理  ,稍后在执行后,向客户端广播交易确认消息和详细信息:

发送

destination:/ queue / trade

content-type:application / json

content-length:44

 

{“action”:“BUY”,“ticker”:“MMM”,“shares”,44} ^ @

目的地的含义在STOMP规范中有意隐藏不透明。它可以是任何字符串,它完全取决于STOMP服务器来定义它们支持的目的地的语义和语法。这是很常见的,但是,对于目的地是路径状串,其中  “/主题/ ..”  意味着发布-订阅(一到多)和  “/队列/”  意味着点至点(一对一个)消息交换。

STOMP服务器可以使用MESSAGE命令向所有订户广播消息。下面是服务器向订阅的客户端发送股票报价的示例:

信息

message-id:nxahklf6-1

订阅:sub-1

目的地:/topic/price.stock.MMM

 

{“ticker”:“MMM”,“price”:129.45} ^ @

重要的是要知道服务器不能发送非请求消息。来自服务器的所有消息必须响应特定的客户端订阅,并且服务器消息的“subscription-id”头必须与客户端订阅的“id”头匹配。

上述概述旨在提供对STOMP协议的最基本的了解。建议完整地查看协议  规范  。

使用STOMP作为WebSocket子协议的好处:

  • 无需发明自定义消息格式
  •  在浏览器中使用现有的  stomp.js客户端
  • 基于目的地路由邮件的能力
  • 选择使用成熟的消息代理,如RabbitMQ,ActiveMQ等进行广播

最重要的是,使用STOMP(vs plain WebSocket)使Spring框架能够以与Spring MVC提供基于HTTP的编程模型相同的方式为应用程序级别提供编程模型。

26.4.2通过WebSocket启用STOMP

Spring框架通过spring-messaging和spring-websocket模块提供了对通过WebSocket使用STOMP的支持。下面是一个在URL路径/文件夹 中显示STOMP WebSocket / SockJS端点的  示例,其中目的地以“/ app”开头的邮件路由到邮件处理方法(即应用程序工作)和目标以“/ topic”或“/ queue”将被路由到消息代理(即广播到其他连接的客户端):

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

 

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

 

    @覆盖

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint(“/ portfolio” ).withSockJS();

    }}

 

    @覆盖

    public void configureMessageBroker(MessageBrokerRegistry config){

        config.setApplicationDestinationPrefixes(“/ app” );

        config.enableSimpleBroker(“/ topic” ,“/ q​​ueue” );

    }}

 

}}

和XML中:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:message-broker application-destination-prefix = “/ app” >

        <websocket:stomp-endpoint path = “/ portfolio” >

            <websocket:sockjs />

        </ websocket:stomp-endpoint>

        <websocket:simple-broker prefix = “/ topic,/ queue” />

    </ websocket:message-broker>

 

</ beans>

[注意]

 

“/ app”前缀是任意的。您可以选择任何前缀。它只是意味着区分消息路由到消息处理方法来做应用程序工作vs消息路由到代理广播到订阅的客户端。

“/ topic”和“/ queue”前缀取决于正在使用的代理。在简单的内存代理的情况下,前缀没有任何特殊意义; 它只是一个指示如何使用目的地的约定(pub-sub针对许多订户或点对点消息,通常针对单个收件人)。在使用专用代理的情况下,大多数代理使用“/ topic”作为具有pub-sub语义的目的地的前缀,并且对于具有点到点语义的目的地使用“/ queue”。检查代理的STOMP页面以查看它支持的目标语义。

 

在浏览器端,客户端可以使用stomp.js  和  sockjs-client如下连接  :

var socket = new SockJS(“/ spring-websocket-portfolio / portfolio” );

var stompClient = Stomp.over(socket);

 

shutpClient.connect({},function(frame){

}}

或者如果通过WebSocket连接(没有SockJS):

var socket = new WebSocket(“/ spring-websocket-portfolio / portfolio” );

var stompClient = Stomp.over(socket);

 

shutpClient.connect({},function(frame){

}}

注意, 上面的  stompClient 不需要指定  登录 和  密码 头。即使它是,它们将被忽略,或者在服务器端被覆盖。有关身份验证的更多信息,请参见  第26.4.8节“连接到全功能代理”  和  第26.4.10节“身份验证”  。

26.4.3消息流

当配置STOMP端点时,Spring应用程序充当连接的客户端的STOMP代理。本节提供了消息在应用程序中的流动情况的全景图。

在  春天的消息 模块提供了异步消息处理的基础。它包含了许多源于Spring Integration  项目的抽象概念,  用于在消息传递应用程序中用作构建块:

  • 消息  - 包含标题和有效内容的消息。
  • MessageHandler  - 处理消息的合同。
  • MessageChannel  - 发送消息的合同,允许发送方和接收方之间的松耦合。
  • SubscribableChannel  - 扩展 MessageChannel 并向已注册的MessageHandler 订阅者发送消息 。
  • ExecutorSubscribableChannel  -一个具体的实施 SubscribableChannel ,可以通过一个线程池,异步消息传送。

该  @EnableWebSocketMessageBroker  Java的配置和  <WebSocket的:消息中介>  XML配置都装配一个具体的消息流。下面是使用简单的内存代理时设置的一部分的图:

消息流简单代理

以上设置包括3个消息通道:

  • “clientInboundChannel”  用于来自WebSocket客户端的消息。
  • “clientOutboundChannel”  用于WebSocket客户端的消息。
  • “brokerChannel”  ,用于从应用程序内部到代理的消息。

相同的三个通道也与专用代理一起使用,除了这里“代理中继”代替简单代理:

消息流代理中继

“clientInboundChannel” 上的消息   可以流向用于应用处理的注释方法(例如股票交易执行请求),或者可以转发到代理(例如,订阅股票报价的客户端)。STOMP目的地用于简单的基于前缀的路由。例如,“/ app”前缀可以将消息路由到注释方法,而“/ topic”和“/ queue”前缀可以将消息路由到代理。

当消息处理注释方法具有返回类型时,其返回值作为Spring 消息的有效载荷发送   到  “brokerChannel” 。代理然后将消息广播到客户端。还可以在消息模板的帮助下从应用程序的任何位置向目标发送消息。例如,HTTP POST处理方法可以向连接的客户端广播消息,或者服务组件可以周期性地广播股票报价。

下面是一个简单的例子来说明消息流:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

 

    @覆盖

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint(“/ portfolio” );

    }}

 

    @覆盖

    public void configureMessageBroker(MessageBrokerRegistry registry){

        registry.setApplicationDestinationPrefixes(“/ app” );

        registry.enableSimpleBroker(“/ topic” );

    }}

 

}}

 

@Controller

public class GreetingController {

 

    @MessageMapping(“/ greeting”) {

    public String handle(String greeting){

        return “[” + getTimestamp()+ “:” + greeting;

    }}

 

}}

以下解释了上述示例的消息流:

  • WebSocket客户端连接到WebSocket端点“/ portfolio”。
  • 订阅“/ topic / greeting”将通过“clientInboundChannel”,并转发到代理。
  • 发送到“/ app / greeting”的问候通过“clientInboundChannel”传递到  GreetingController 。控制器添加当前时间,并且返回值通过“brokerChannel”作为消息传递到“/ topic / greeting”(基于约定选择目的地,但是可以通过  @SendTo重写)。
  • 代理将广播消息给订阅者,他们通过  “clientOutboundChannel” 。

下一节提供了有关注释方法的更多详细信息,包括支持的参数和返回值的种类。

26.4.4注释消息处理

该  @MessageMapping  注释支持的方法  @Controller  类。它可以用于映射方法到消息目的地,也可以与类型级别@MessageMapping 组合,   用于表示控制器中所有注释方法的共享映射。

默认情况下,目标映射被视为Ant风格,斜线分隔的路径模式,例如“/ foo *”,“/ foo / **”。它们还可以包含模板变量,例如“/ foo / {id}”,然后可以通过  @DestinationVariable 注释方法参数引用。

[注意]

 

应用程序还可以使用点分隔目标(vs斜杠)。请参见  第26.4.9节“在@MessageMapping目标中使用点作为分隔符”

 

@MessageMapping方法支持  以下 方法参数:

  • 消息 方法参数,以访问正在处理的完整消息。
  • @Payload -annotated参数,用于访问消息的有效内容,使用  org.springframework.messaging.converter.MessageConverter转换。不需要注释的存在,因为它是默认假设的。使用验证注释注释的Payload方法参数(如  @Validated )将受JSR-303验证。
  • @Header -annotated参数,用于访问特定的头值以及类型转换, 如果需要,使用  org.springframework.core.convert.converter.Converter。
  • @Headers -annotated方法参数,必须也可以分配给  java.util.Map  以访问消息中的所有头。
  • MessageHeaders  方法参数,用于访问所有标头的映射。
  • MessageHeaderAccessor ,  SimpMessageHeaderAccessor 或  StompHeaderAccessor  通过类型访问器方法访问头。
  • @DestinationVariable - 注释参数,用于访问从消息目标提取的模板变量。根据需要,值将转换为声明的方法参数类型。
  • 反映 在WebSocket HTTP握手时登录的用户的java.security.Principal 方法参数。

来自@MessageMapping  方法  的返回值使用  org.springframework.messaging.converter.MessageConverter 转换 ,并用作新消息的主体,然后默认情况下将其发送到 与客户端具有相同目标的  “brokerChannel” 消息,但 默认使用前缀  “/ topic” 。一个  @SendTo  消息级别注解可以用来指定任何其他目的来代替。它也可以设置一个类级别共享一个公共目的地。

一个  @SubscribeMapping  注释也可用于映射订阅请求  @Controller  方法。它在方法级别受支持,但也可以与类型级别@MessageMapping  注释组合,该  注释表示同一控制器中所有消息处理方法的共享映射。

默认情况下,来自@SubscribeMapping  方法的返回值  作为消息直接发送回连接的客户端,并且不通过代理。这对于实现请求 - 应答消息交互是有用的; 例如,在应用UI被初始化时获取应用数据。或者可选地一个  @SubscribeMapping  方法可以与注释  @SendTo  在这种情况下,所得到的消息被发送到  “brokerChannel”  使用指定的目标目的地。

[注意]

 

在某些情况下,控制器可能需要在运行时使用AOP代理进行修饰。一个例子是,如果你选择让  @Transactional  直接在控制器上的注释。在这种情况下,对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。然而,如果控制器必须实现不是Spring Context回调的接口(例如  InitializingBean ,  * Aware 等),您可能需要显式配置基于类的代理。例如,使用  <tx:annotation-driven /> ,更改为  <tx:annotation-driven proxy-target-class =“true”/> 。

 

26.4.5发送消息

如果你想从应用程序的任何部分发送消息到连接的客户端怎么办?任何应用程序组件都可以向“brokerChannel” 发送消息  。最简单的方法是 注入一个  SimpMessagingTemplate ,并使用它来发送消息。通常它应该很容易通过类型注入,例如:

@Controller

public class GreetingController {

 

    私有 SimpMessagingTemplate模板;

 

    @Autowired

    public GreetingController(SimpMessagingTemplate template){

        this .template = template;

    }}

 

    @RequestMapping(path =“/ greetings”,method = POST)

    public void greet(String greeting){

        String text = “[” + getTimestamp()+ “]:” + greeting;

        this .template.convertAndSend(“/ topic / greetings” ,text);

    }}

 

}}

但是如果存在相同类型的另一个bean,它也可以通过其名称“brokerMessagingTemplate”限定。

26.4.6简单代理

内置的简单消息代理处理来自客户端的订阅请求,将它们存储在内存中,并向匹配目标的连接客户端广播消息。代理支持类似路径的目标,包括对Ant风格目标模式的订阅。

[注意]

 

应用程序还可以使用点分隔目标(vs斜杠)。请参见  第26.4.9节“在@MessageMapping目标中使用点作为分隔符”

 

26.4.7全功能代理

简单的代理是很好的入门,但只支持STOMP命令的一个子集(例如没有ack,收据等),依赖于一个简单的消息发送循环,并且不适合集群。作为替代,应用程序可以升级到使用全功能消息代理。

检查您的消息代理的STOMP文档(例如  RabbitMQ,  ActiveMQ等),安装代理,并启用STOMP支持运行它。然后在Spring配置而不是简单代理中启用STOMP代理中继。

以下是启用全功能代理的示例配置:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

 

    @覆盖

    public void registerStompEndpoints(StompEndpointRegistry registry){

        registry.addEndpoint(“/ portfolio” ).withSockJS();

    }}

 

    @覆盖

    public void configureMessageBroker(MessageBrokerRegistry registry){

        registry.enableStompBrokerRelay(“/ topic” ,“/ q​​ueue” );

        registry.setApplicationDestinationPrefixes(“/ app” );

    }}

 

}}

XML配置等效:

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:message-broker application-destination-prefix = “/ app” >

        <websocket:stomp-endpoint path = “/ portfolio” />

            <websocket:sockjs />

        </ websocket:stomp-endpoint>

        <websocket:stomp-broker-relay prefix = “/ topic,/ queue” />

    </ websocket:message-broker>

 

</ beans>

上述配置中的“STOMP代理中继”是一个Spring  MessageHandler  ,它通过将消息转发到外部消息代理来处理消息。为此,它建立到代理的TCP连接,转发所有消息到它,然后通过他们的WebSocket会话将从代理接收的所有消息转发到客户端。本质上,它作为“中继”,在两个方向上转发消息。

[注意]

 

Spring使用  org.projectreactor:reactor-net  和  io.netty:netty-all  来管理与代理的TCP连接,这两者都需要作为项目依赖关系添加。

Spring Framework 4.3.x中的STOMP代理支持与Reactor的2.0.x版本兼容。因此,它不支持与 需要Reactor 3.x 的  弹簧云流反应模块相结合。

Spring Framework 5依赖于Reactor 3和Reactor Netty,它具有独立的版本控制,用于到STOMP代理的TCP连接,而且还为反应式编程模型提供广泛的支持。

 

此外,应用程序组件(例如HTTP请求处理方法,业务服务等)也可以向代理中继发送消息,如 26.4.5  节“发送消息”中所述,以便向订阅的WebSocket客户端广播消息。

实际上,代理中继实现鲁棒且可扩展的消息广播。

26.4.8连接到全功能代理

STOMP代理中继维护到代理的单个“系统”TCP连接。此连接用于仅来自服务器端应用程序的消息,而不用于接收消息。您可以配置此连接的STOMP凭据,即STOMP帧  登录 和  密码 头。这在XML命名空间和Java配置中显示为  systemLogin / systemPasscode  属性,默认值为  guest / guest 。

STOMP代理中继还为每个连接的WebSocket客户端创建单独的TCP连接。您可以配置STOMP凭据以用于代表客户端创建的所有TCP连接。这在XML命名空间和Java配置中公开为  clientLogin / clientPasscode  属性,默认值为  guest / guest 。

[注意]

 

STOMP代理中继始终 在代理客户端转发到代理的 每个CONNECT 帧上设置  登录 和  密码头  。因此WebSocket客户端不需要设置这些头; 它们将被忽略。如下面部分所述,WebSocket客户端应该依赖HTTP身份验证来保护WebSocket端点并建立客户端身份。

 

STOMP代理中继还通过“系统”TCP连接向消息代理发送心跳和从消息代理接收心跳。您可以配置发送和接收心跳的间隔(默认为10秒)。如果与代理的连接丢失,代理中继将继续尝试重新连接,每5秒,直到成功。

[注意]

 

Spring bean可以实现  ApplicationListener <BrokerAvailabilityEvent>  ,以便在与代理的“系统”连接丢失并重新建立时接收通知。例如,当没有活动的“系统”连接时,股票报价服务广播股票报价可以停止尝试发送消息。

 

STOMP代理中继也可以使用  virtualHost  属性进行配置。该属性的值将被设置为 每个CONNECT  帧的  主机头,  并且可以例如在云环境中是有用的,其中建立TCP连接的实际主机不同于提供基于云的STOMP服务的主机。

26.4.9在@MessageMapping Destinations中使用Dot作为分隔符

虽然斜线分隔的路径模式对于Web开发人员来说是熟悉的,但在消息传递中通常使用“。” 作为分隔符,例如以主题,队列,交换等名称。应用程序也可以切换到使用“。”。(点)而不是“/”(斜杠)作为@MessageMapping  映射中的分隔符,  通过配置自定义  AntPathMatcher 。

在Java配置中:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

 

  // ...

 

  @覆盖

  public void configureMessageBroker(MessageBrokerRegistry registry){

    registry.enableStompBrokerRelay(“/ q​​ueue /” ,“/ topic /” );

    registry.setApplicationDestinationPrefixes(“/ app” );

    registry.setPathMatcher(new AntPathMatcher(“。” ));

  }}

 

}}

在XML配置中:

<beans xmlns = “http://www.springframework.org/schema/beans”

  xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

  xmlns:websocket = “http://www.springframework.org/schema/websocket”

  xsi:schemaLocation = “

    http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd

    http://www.springframework.org/schema/websocket

    http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

  <websocket:message-broker application-destination-prefix = “/ app” path-matcher = “pathMatcher” >

    <websocket:stomp-endpoint path = “/ stomp” />

    <websocket:simple-broker prefix = “/ topic,/ queue” />

  </ websocket:message-broker>

 

  <bean id = “pathMatcher” class = “org.springframework.util.AntPathMatcher” >

    <constructor-arg index = “0” value = “。” />

  </ bean>

 

</ beans>

下面是一个简单的例子来说明一个带有“”的控制器。分隔器:

@Controller

@MessageMapping(“foo”)

public class FooController {

 

  @MessageMapping(“bar。{baz}”)

  public void handleBaz(@DestinationVariable String baz){

  }}

 

}}

如果应用程序前缀设置为“/ app”,那么foo方法将有效地映射到“/app/foo.bar.{baz}”。

26.4.10认证

每个通过WebSocket消息传递会话的STOMP都以HTTP请求开始 - 可以是请求升级到WebSockets(即WebSocket握手),或者在SockJS回退一系列SockJS HTTP传输请求的情况下。

Web应用程序已经具有身份验证和授权以保护HTTP请求。通常,用户通过Spring Security使用某些机制(例如登录页面,HTTP基本认证或其他)进行认证。已认证用户的安全上下文保存在HTTP会话中,并与同一基于Cookie的会话中的后续请求相关联。

因此,对于WebSocket握手,或对于SockJS HTTP传输请求,通常已经存在通过HttpServletRequest#getUserPrincipal()可访问的已认证用户。Spring会自动将该用户与为其创建的WebSocket或SockJS会话相关联,随后与通过该会话通过用户头传输的所有STOMP消息相关联。

简而言之,没有什么特别的典型的Web应用程序需要做,以上和它已经为安全。用户在HTTP请求级别通过基于cookie的HTTP会话维护的安全上下文进行认证,然后该会话与为该用户创建的WebSocket或SockJS会话相关联,并且导致在 流过应用的每个消息上标记用户标题  。

注意,STOMP协议在CONNECT  帧上有一个“登录”和“通行码”标题  。这些是最初设计用于和仍然需要例如对于STOMP over TCP。但是对于STOMP over WebSocket默认情况下Spring忽略在STOMP协议级别的授权头,并假设用户已经在HTTP传输级别进行身份验证,并期望WebSocket或SockJS会话包含已验证的用户。

[注意]

 

Spring Security提供  的WebSocket子协议授权  的使用  ChannelInterceptor  授权根据他们的用户头的消息。Spring Session还提供了一个  WebSocket集成  ,以确保当WebSocket会话仍处于活动状态时,用户HTTP会话不会过期。

 

26.4.11基于令牌的认证

Spring Security OAuth  提供对基于令牌的安全性的支持,包括JSON Web令牌(JWT)。这可以用作Web应用程序中的认证机制,包括基于WebSocket交互的STOMP,如上一节所述,即通过基于cookie的会话维护身份。

同时,基于cookie的会话并不总是最适合例如在不希望维护服务器端会话的应用中或者在通常使用头部进行认证的移动应用中。

该  WebSocket协议RFC 6455  “没有规定说服务器可以WebSocket的握手期间验证客户端的任何特定方式。” 然而实际上,浏览器客户端只能使用标准认证报头(即基本的HTTP认证)或cookie,并且不能例如提供自定义报头。同样,SockJS JavaScript客户端不提供使用SockJS传输请求发送HTTP头的方法,请参见  sockjs-client问题196。相反,它允许发送可用于发送令牌但具有其自身缺点的查询参数,例如,令牌可能无意中用服务器日志中的URL记录。

[注意]

 

上述限制适用于基于浏览器的客户端,不适用于基于Spring Java的STOMP客户端,它支持使用WebSocket和SockJS请求发送标头。

 

因此,希望避免使用Cookie的应用程序可能没有任何好的替代方案来在HTTP协议级别进行身份验证。而不是使用cookie,他们可能更喜欢使用STOMP消息协议级别的头进行身份验证有两个简单的步骤:

1.    使用STOMP客户端在连接时传递身份验证头。

2.    工艺认证头(S)与  ChannelInterceptor 。

下面是注册自定义认证拦截器的示例服务器端配置。请注意,拦截器只需要验证并设置CONNECT 消息上的用户头  。Spring将注意并保存经过身份验证的用户,并将其与后续STOMP消息关联在同一会话上:

@组态

@EnableWebSocketMessageBroker

public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {

 

  @覆盖

  public void configureClientInboundChannel(ChannelRegistration registration){

    registration.setInterceptors(new ChannelInterceptorAdapter(){

 

        @覆盖

        public Message <?> preSend(Message <?> message,MessageChannel channel){

 

            StompHeaderAccessor accessor =

                MessageHeaderAccessor.getAccessor(消息,StompHeaderAccessor );

 

            if(StompCommand.CONNECT.equals(accessor.getCommand())){

                Principal user = ...; //访问认证头

                accessor.setUser(user);

            }}

 

            返回消息;

        }}

    });

  }}

}}

还要注意,当使用Spring Security的消息授权时,目前你需要确保认证  ChannelInterceptor  配置在Spring Security的前面。这最好通过在标记为  @Order(Ordered.HIGHEST_PRECEDENCE + 99)的AbstractWebSocketMessageBrokerConfigurer 的自己的子类中声明自定义拦截器  。

26.4.12用户目的地

应用程序可以发送针对特定用户的消息,并且Spring的STOMP支持为此目的识别前缀为  “/ user /”  的目标。例如,客户端可能订阅目标  “/ user / queue / position-updates” 。此目的地将由处理  UserDestinationMessageHandler 并转化到特有的用户会话的目的地,例如,  “/队列/位置更新- user123” 。这提供了订阅通用命名目的地的便利,同时确保与订阅相同目的地的其他用户没有冲突,使得每个用户可以接收唯一的库存位置更新。

在发送侧,消息可以被发送到目的地,诸如  “/ user / {username} / queue / position-updates” ,其又将由  UserDestinationMessageHandler  转换成一个或多个目的地,用户。这允许应用程序内的任何组件发送针对特定用户的消息,而不必知道比它们的名称和通用目的地更多的东西。这也通过注释以及消息传递模板来支持。

例如,消息处理方法可以向与通过@SendToUser  注释处理的消息相关联的用户发送消息  (在类级别上也支持共享公共目的地):

@Controller

public class PortfolioController {

 

    @MessageMapping(“/ trade”)

    @SendToUser(“/ queue / position-updates”)

    public TradeResult executeTrade(贸易,主要委托人){

        // ...

        return tradeResult;

    }}

}}

如果用户有多个会话,默认情况下,所有订阅到给定目标的会话都是目标。但是有时,可能有必要仅针对发送正在处理的消息的会话。这可以通过将broadcast  属性设置  为false来实现,例如:

@Controller

public class MyController {

 

    @MessageMapping(“/ action”)

    public void handleAction()throws Exception {

        // raise MyBusinessException这里

    }}

 

    @MessageExceptionHandler

    @SendToUser(destinations =“/ queue / errors”,broadcast = false)

    public ApplicationError handleException(MyBusinessException exception){

        // ...

        return appError;

    }}

}}

[注意]

 

虽然用户目的地通常暗示已认证的用户,但并不严格要求。与认证用户不相关联的WebSocket会话可以订阅用户目标。在这种情况下,  @SendToUser  注释的行为将与broadcast = false 完全相同  ,即仅定位发送正在处理的消息的会话。

 

还可以通过注入 由Java配置或XML命名空间创建的SimpMessagingTemplate(例如,bean名称为  “brokerMessagingTemplate”, 如果需要使用@Qualifier进行  限定)从任何应用程序组件向用户目标发送消息  :

@服务

public class TradeServiceImpl implements TradeService {

 

       private final SimpMessagingTemplate messagingTemplate;

 

       @Autowired

       public TradeServiceImpl(SimpMessagingTemplate messagingTemplate){

              this .messagingTemplate = messagingTemplate;

       }}

 

       // ...

 

       public void afterTradeExecuted(trade trade){

              this .messagingTemplate.convertAndSendToUser(

                            trade.getUserName(), “/ q​​ueue / position-updates” ,trade.getResult());

       }}

}}

[注意]

 

与外部消息代理一起使用用户目标时,请检查代理文档,了解如何管理非活动队列,以便在用户会话结束时,删除所有唯一用户队列。例如,当使用/exchange/amq.direct/position-updates等目的地时,RabbitMQ会创建自动删除队列   。所以在这种情况下,客户端可以订阅  /user/exchange/amq.direct/position-updates 。类似地,ActiveMQ具有   用于清除非活动目的地的配置选项

 

在多应用程序服务器方案中,用户目标可能仍未解决,因为用户连接到不同的服务器。在这种情况下,您可以配置目标以广播未解析的消息,以便其他服务器有机会尝试。这可以通过 Java配置中MessageBrokerRegistry 的  userDestinationBroadcast  属性和 XML中消息代理元素的  user-destination-broadcast  属性来完成  。

26.4.13监听ApplicationContext事件和拦截消息

几个  ApplicationContext  事件(下面列出)被发布,并且可以通过实现Spring的ApplicationListener  接口来接收  。

  • BrokerAvailabilityEvent  - 指示代理何时可用/不可用。虽然“简单”代理在启动时立即可用并且在应用正在运行时仍然可用,但是例如如果代理被重新启动,则STOMP“代理中继”可能失去与全功能代理的连接。代理中继具有重新连接逻辑,并且将在其返回时重新建立到代理的“系统”连接,因此,每当状态从连接改变为断开时,该事件被发布,反之亦然。使用  SimpMessagingTemplate的组件 应该订阅此事件,并避免在代理不可用时发送消息。在任何情况下,他们应该准备 在发送消息时处理  MessageDeliveryException 。
  • SessionConnectEvent  - 在接收到指示新客户端会话开始的新STOMP CONNECT时发布。事件包含表示连接的消息,包括会话ID,用户信息(如果有)以及客户端可能已发送的任何自定义标头。这对于跟踪客户端会话很有用。订阅此事件的组件可以使用SimpMessageHeaderAccessor  或  StompMessageHeaderAccessor 包装所包含的消息  。
  • SessionConnectedEvent  - 在SessionConnectEvent后不久  发布, 当代理发送一个STOMP CONNECTED帧响应CONNECT。在这一点上,STOMP会话可以被认为是完全建立的。
  • SessionSubscribeEvent  - 在接收到新的STOMP SUBSCRIBE时发布。
  • SessionUnsubscribeEvent  - 在收到新的STOMP UNSUBSCRIBE时发布。
  • SessionDisconnectEvent  - 在STOMP会话结束时发布。DISCONNECT可能已从客户端发送,或者也可能在WebSocket会话关闭时自动生成。在某些情况下,此事件可能会每个会话发布多次。组件对于多个断开事件应该是幂等的。

[注意]

 

当使用全功能代理时,如果代理暂时不可用,STOMP“代理中继”会自动重新连接“系统”连接。但是客户端连接不会自动重新连接。假设心跳已启用,客户端通常会注意到代理在10秒内没有响应。客户端需要实现自己的重新连接逻辑。

 

此外,应用程序可以通过注册直接截取每个传入和传出的消息  ChannelInterceptor  各自的信息渠道上。例如,拦截入站消息:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

 

  @覆盖

  public void configureClientInboundChannel(ChannelRegistration registration){

    registration.setInterceptors(new MyChannelInterceptor());

  }}

}}

自定义  ChannelInterceptor  可以扩展空方法基类  ChannelInterceptorAdapter  ,并使用  StompHeaderAccessor  或  SimpMessageHeaderAccessor  访问有关消息的信息。

public class MyChannelInterceptor extends ChannelInterceptorAdapter {

 

  @覆盖

  public Message <?> preSend(Message <?> message,MessageChannel channel){

    StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

    StompCommand command = accessor.getStompCommand();

    // ...

    返回消息;

  }}

}}

26.4.14 STOMP客户端

Spring通过WebSocket客户端和STOMP over TCP客户端提供STOMP。

要开始创建和配置  WebSocketStompClient :

WebSocketClient webSocketClient = new StandardWebSocketClient();

WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);

stompClient.setMessageConverter(new StringMessageConverter());

stompClient.setTaskScheduler(taskScheduler); // for heartbeats

在上面的示例中,  StandardWebSocketClient  可以替换为  SockJsClient, 因为它也是WebSocketClient的  实现。该  SockJsClient  可以使用的WebSocket或基于HTTP的运输作为后备。有关更多详细信息,请参见  第26.3.7节“SockJS客户端”

接下来建立连接并提供STOMP会话的处理程序:

String url = “ws://127.0.0.1:8080 / endpoint” ;

StompSessionHandler sessionHandler = new MyStompSessionHandler();

stompClient.connect(url,sessionHandler);

当会话准备好使用时,通知处理程序:

public class MyStompSessionHandler extends StompSessionHandlerAdapter {

 

    @覆盖

    public void afterConnected(StompSession session,StompHeaders connectedHeaders){

        // ...

    }}

}}

一旦会话建立,任何有效负载可以被发送,并且将与配置的MessageConverter  序列化:

session.send(“/ topic / foo” ,“payload” );

您还可以订阅目的地。的  订阅 方法需要用于在订阅消息的处理程序,并返回一个  订阅 可以用于退订手柄。对于每个接收到的消息,处理程序可以指定目标对象类型,有效载荷应该反序列化为:

session.subscribe(“/ topic / foo” ,new StompFrameHandler(){

 

    @覆盖

    public Type getPayloadType(StompHeaders headers){

        返回 String。 ;

    }}

 

    @覆盖

    public void handleFrame(StompHeaders headers,Object payload){

        // ...

    }}

 

});

以使单块心跳配置  WebSocketStompClient  用  的TaskScheduler  和任选自心跳间隔10秒,导致心跳写入不活动待发送和10秒读出的活动而关闭连接。

[注意]

 

当使用  WebSocketStompClient  进行性能测试以从同一台机器上模拟成千上万个客户端时,请考虑关闭检测信号,因为每个连接都会调度自己的心跳任务,并且未针对在同一台机器上运行的大量客户端进行优化。

 

STOMP协议还支持接收,其中客户端必须在处理发送或订阅之后添加服务器用RECEIPT帧响应的“接收”报头。为了支持这个  StompSession  提供  setAutoReceipt(布尔) ,导致“收据”头被添加到每个后续的发送或订阅。另外,您也可以手动将“回执”头添加到  StompHeaders 。发送和订阅返回的实例  Receiptable  可用于向用于接收的成功和失败回调注册。对于此功能,客户端必须配置  TaskScheduler  和收据到期之前的时间量(默认为15秒)。

需要注意的是  StompSessionHandler  本身就是一个  StompFrameHandler  这使得它能够处理错误的帧除了  handleException  回调从消息的处理异常和  handleTransportError  运输级别的错误,包括  ConnectionLostException 。

26.4.15 WebSocket范围

每个WebSocket会话都有一个属性映射。映射作为报头附加到入站客户端消息,并且可以从控制器方法访问,例如:

@Controller

public class MyController {

 

    @MessageMapping(“/ action”)

    public void handle(SimpMessageHeaderAccessor headerAccessor){

        Map <String,Object> attrs = headerAccessor.getSessionAttributes();

        // ...

    }}

}}

也可以在websocket  范围中声明一个Spring管理的bean  。WebSocket范围的bean可以注入到控制器和在“clientInboundChannel”上注册的任何通道拦截器。这些通常是单身,并且比任何单独的WebSocket会话更长的活。因此,您将需要为WebSocket范围的bean使用范围代理模式:

@零件

@Scope(scopeName =“websocket”,proxyMode = ScopedProxyMode.TARGET_CLASS)

public class MyBean {

 

    @PostConstruct

    public void init(){

        //依赖注入后调用

    }}

 

    // ...

 

    @PreDestroy

    public void destroy(){

        //当WebSocket会话结束时调用

    }}

}}

 

@Controller

public class MyController {

 

    private final MyBean myBean;

 

    @Autowired

    public MyController(MyBean myBean){

        这个 .myBean = myBean;

    }}

 

    @MessageMapping(“/ action”)

    public void handle(){

        // this.myBean从当前的WebSocket会话

    }}

}}

与任何自定义作用域一样,Spring  在第一次从控制器访问时初始化一个新的  MyBean 实例,并将实例存储在WebSocket会话属性中。随后返回相同的实例,直到会话结束。WebSocket范围的bean将调用所有Spring生命周期方法,如上面的示例所示。

26.4.16配置和性能

关于性能,没有银弹。许多因素可能影响它,包括消息的大小,卷,应用方法是否执行需要阻止的工作,以及外部因素,如网络速度和其他。本节的目标是提供可用配置选项的概述以及关于如何推理缩放的一些想法。

在消息传递应用程序中,消息通过线程池支持的异步执行的通道传递。配置这样的应用需要对信道和消息流的良好了解。因此,建议查看  第26.4.3节“消息流”

最明显的地方是配置支持“clientInboundChannel”  和  “clientOutboundChannel” 的线程池  。默认情况下,两个都配置为可用处理器数量的两倍。

如果在带注释的方法中消息的处理主要是CPU限制,那么“clientInboundChannel” 的线程数   应该保持接近处理器的数量。如果他们做的工作是更多的IO绑定,并要求阻塞或等待数据库或其他外部系统,则线程池大小将需要增加。

[注意]

 

ThreadPoolExecutor  有3个重要的属性。这些是核心和最大线程池大小以及队列存储没有可用线程的任务的容量。

一个常见的混乱点是配置核心池大小(例如10)和最大池大小(例如20)会导致线程池有10到20个线程。实际上,如果容量保持为其默认值Integer.MAX_VALUE,则线程池将永远不会增加超过核心池大小,因为所有其他任务将被排队。

请查看ThreadPoolExecutor 的Javadoc   以了解这些属性的工作原理,并了解各种排队策略。

 

在  “clientOutboundChannel”  端,它是关于向WebSocket客户端发送消息。如果客户端在快速网络上,则线程数应保持接近可用处理器的数量。如果它们速度较慢或带宽较低,则消耗消息和占用线程池的时间会更长。因此,增加线程池大小是必要的。

虽然“clientInboundChannel”的工作负载可能预测 - 毕竟它是基于应用程序做什么 - 如何配置“clientOutboundChannel”更难,因为它是基于超出应用程序的控制的因素。因此,有两个与发送消息相关的附加属性。这些是  “sendTimeLimit” 和  “sendBufferSizeLimit” 。这些用于配置允许发送多长时间,以及在向客户端发送消息时可缓冲多少数据。

一般的想法是在任何给定的时间只有单个线程可以用于发送到客户端。所有其他消息同时被缓冲,您可以使用这些属性来决定允许发送消息的时间以及可以同时缓冲多少数据。有关重要的其他详细信息,请查看此配置的Javadoc和XML模式的文档。

这里是示例配置:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

 

    @覆盖

    public void configureWebSocketTransport(WebSocketTransportRegistration registration){

        registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);

    }}

 

    // ...

 

}}

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:message-broker>

        <websocket:transport send-timeout = “15000” send-buffer-size = “524288” />

        <! - ... - >

    </ websocket:message-broker>

 

</ beans>

上面显示的WebSocket传输配置也可用于配置传入STOMP消息的最大允许大小。虽然理论上WebSocket消息的大小几乎是无限的,但实际上WebSocket服务器强加了限制 - 例如,Tomcat上的8K和Jetty上的64K。为此,STOMP客户端(如stomp.js)在16K边界处分割较大的STOMP消息,并将其作为多个WebSocket消息发送,因此需要服务器进行缓冲和重新组合。

Spring的STOMP over WebSocket支持这样做,所以应用程序可以配置STOMP消息的最大大小,而不考虑WebSocket服务器特定的消息大小。请记住,WebSocket消息大小将自动调整,如果有必要,以确保他们可以携带16K WebSocket消息至少。

这里是示例配置:

@组态

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

 

    @覆盖

    public void configureWebSocketTransport(WebSocketTransportRegistration registration){

        registration.setMessageSizeLimit(128 * 1024);

    }}

 

    // ...

 

}}

<beans xmlns = “http://www.springframework.org/schema/beans”

    xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance”

    xmlns:websocket = “http://www.springframework.org/schema/websocket”

    xsi:schemaLocation = “

        http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/websocket

        http://www.springframework.org/schema/websocket/spring-websocket.xsd“ >

 

    <websocket:message-broker>

        <websocket:transport message-size = “131072” />

        <! - ... - >

    </ websocket:message-broker>

 

</ beans>

关于缩放的一个重要点是使用多个应用程序实例。目前,不可能使用简单的代理。然而,当使用诸如RabbitMQ的全功能代理时,每个应用实例连接到代理,并且从一个应用实例广播的消息可以通过代理广播到通过任何其他应用实例连接的WebSocket客户端。

26.4.17运行时监控

当使用  @EnableWebSocketMessageBroker  或  <websocket:message-broker>  关键基础结构组件自动收集统计信息和计数器,提供对应用程序内部状态的重要洞察。配置还声明一个类型为WebSocketMessageBrokerStats 的bean,   它在一个地方收集所有可用的信息,默认情况下 每30分钟将它以INFO 级别记录  一次。这个bean可以通过Spring的MBeanExporter 导出到JMX   ,以便在运行时查看,例如通过JDK的  jconsole 。以下是可用信息的摘要。

客户端WebSocket会话

当前

指示当前有多少客户端会话,计数由WebSocket vs HTTP流和轮询SockJS会话进一步细分。

表示已建立的会话总数。

异常关闭

连接失败

这些是建立的会话,但在60秒内没有收到任何消息之后关闭。这通常是代理或网络问题的指示。

超过发送限制

会话在超过配置的发送超时或慢客户端可能发生的发送缓冲区限制后关闭(请参阅上一节)。

传输错误

会话在传输错误(例如无法读取或写入WebSocket连接或HTTP请求/响应)后关闭。

STOMP帧

已处理的CONNECT,CONNECTED和DISCONNECT帧的总数指示在STOMP级别上连接多少个客户端。请注意,当会话异常关闭或客户端关闭而不发送DISCONNECT帧时,DISCONNECT计数可能会降低。

STOMP代理继电器

TCP连接

表示代表客户端WebSocket会话建立到代理的多少个TCP连接。这应该等于客户端WebSocket会话的数量+用于从应用程序内发送消息的另外的共享“系统”连接。

STOMP帧

代表客户端转发到代理或从代理接收的CONNECT,CONNECTED和DISCONNECT帧的总数。请注意,不管如何关闭客户端WebSocket会话,都会向代理发送DISCONNECT帧。因此,较低的DISCONNECT帧计数是代理正在主动关闭连接的指示,可能是由于未及时到达的心跳,无效的输入帧或其他。

客户端入站通道

stats从线程池支持“clientInboundChannel”,提供对传入消息处理的健康状况的洞察。在这里排队的任务是指示应用可能太慢来处理消息。如果有I / O绑定任务(例如慢速数据库查询,HTTP请求到第三方REST API等),则考虑增加线程池大小。

客户端出站通道

stats从线程池支持“clientOutboundChannel”,提供对广播消息到客户端的健康状况的洞察。这里排队的任务是指示客户端太慢而不能消费消息。解决这个问题的一种方法是增加线程池大小以适应预期的并发慢客户端的数量。另一个选项是减少发送超时和发送缓冲区大小限制(参见上一节)。

SockJS任务计划程序

来自用于发送心跳的SockJS任务调度程序的线程池的统计信息。请注意,当在STOMP级别协商心跳时,将禁用SockJS心跳。

26.4.18测试注释控制器方法

有两种主要方法来使用Spring的STOMP over WebSocket支持来测试应用程序。第一个是编写服务器端测试,验证控制器的功能及其注释的消息处理方法。第二是编写涉及运行客户端和服务器的完整的端到端测试。

这两种方法不是相互排斥的。相反,每个人在一个总体测试策略中有一个地方。服务器端测试更加集中,更易于编写和维护。另一方面,端到端集成测试更加完整和测试更多,但他们也更参与编写和维护。

服务器端测试的最简单形式是写入控制器单元测试。但是这不是很有用,因为控制器的大部分功能取决于它的注释。纯单元测试根本不能测试。

理想情况下,测试中的控制器应该像运行时一样被调用,就像使用Spring MVC Test框架测试控制器处理HTTP请求的方法一样。即没有运行Servlet容器,而是依赖于Spring框架来调用注释的控制器。就像Spring MVC Test这里有两个可能的选择,使用“基于上下文”或“独立”设置:

1.    在Spring TestContext框架的帮助下加载实际的Spring配置,注入“clientInboundChannel”作为测试字段,并使用它来发送要由控制器方法处理的消息。

2.    手动设置调用控制器所需的最小Spring框架基础结构(即  SimpAnnotationMethodMessageHandler ),并将控制器的消息直接传递给它。

这两种设置方案都在股票投资组合  示例应用程序的  测试中展示。

第二种方法是创建端到端集成测试。为此,您需要在嵌入式模式下运行WebSocket服务器,并作为发送包含STOMP帧的WebSocket消息的WebSocket客户端连接到该服务器。对股票投资组合  示例应用程序的  测试还演示了使用Tomcat作为嵌入式WebSocket服务器和简单的STOMP客户端的测试方法。

 

标签: Spring websocket
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
GuoMengYue
粉丝 31
博文 14
码字总数 129268
作品 2
×
GuoMengYue
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: