WebSocket API
Spring Framework提供了一个WebSocket API,您可以使用它来编写处理WebSocket消息的客户端和服务器端应用程序。
WebSocketHandler
创建WebSocket服务器就像实现WebSocketHandler
一样简单,或者更可能是扩展TextWebSocketHandler
或BinaryWebSocketHandler
。以下示例使用TextWebSocketHandler
:
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
有专门的WebSocket Java配置和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;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://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服务环境中相对简单。
在直接使用WebSocketHandler
API与间接使用(例如通过STOMP消息传递)之间,应用程序必须同步发送消息,因为底层标准WebSocket会话(JSR-356)不允许并发发送。一种选择是使用ConcurrentWebSocketSessionDecorator
来包装WebSocketSession
。
WebSocket握手
自定义初始HTTP WebSocket握手请求的最简单方法是通过HandshakeInterceptor
,它公开了“握手”前和“握手”后的方法。您可以使用此拦截器来阻止握手或使任何属性可用于WebSocketSession
。以下示例使用内置拦截器将HTTP会话属性传递给WebSocket会话:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://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服务器引擎和版本(有关更多信息,请参见部署),则可能还需要使用此选项。Java配置和XML命名空间都可以配置自定义HandshakeHandler
。
Spring提供了一个WebSocketHandlerDecorator 基类,您可以使用它来为WebSocketHandler 添加额外的行为。在使用WebSocket Java配置或XML命名空间时,默认提供了日志记录和异常处理实现。ExceptionWebSocketHandlerDecorator 捕获所有未捕获的异常,这些异常来自任何WebSocketHandler 方法,并使用状态1011 关闭WebSocket会话,表示服务器错误。 |
部署
Spring WebSocket API 很容易集成到 Spring MVC 应用程序中,其中 DispatcherServlet
既提供 HTTP WebSocket 握手,也处理其他 HTTP 请求。通过调用 WebSocketHttpRequestHandler
,也可以很容易地集成到其他 HTTP 处理场景中。这种方式方便且易于理解。然而,关于 JSR-356 运行时存在特殊考虑。
Jakarta WebSocket API (JSR-356) 提供了两种部署机制。第一种是在启动时进行 Servlet 容器类路径扫描(Servlet 3 功能)。另一种是在 Servlet 容器初始化时使用注册 API。这两种机制都不支持使用单个“前端控制器”来处理所有 HTTP 处理,包括 WebSocket 握手和其他所有 HTTP 请求,例如 Spring MVC 的 DispatcherServlet
。
这是 JSR-356 的一个重要限制,Spring 的 WebSocket 支持通过针对服务器的 RequestUpgradeStrategy
实现来解决这个问题,即使在 JSR-356 运行时也是如此。目前,这样的策略已经存在于 Tomcat、Jetty、GlassFish、WebLogic、WebSphere 和 Undertow(以及 WildFly)中。在 Jakarta WebSocket 2.1 中,提供了一个标准的请求升级策略,Spring 在 Jakarta EE 10 基于的 Web 容器(如 Tomcat 10.1 和 Jetty 12)上选择使用。
第二个考虑因素是,具有 JSR-356 支持的 Servlet 容器预计会执行 ServletContainerInitializer
(SCI)扫描,这可能会减慢应用程序的启动速度,有时甚至会显著减慢。如果在升级到支持 JSR-356 的 Servlet 容器版本后观察到显著影响,可以通过在 web.xml
中使用 <absolute-ordering />
元素有选择地启用或禁用 Web 片段(和 SCI 扫描),如下例所示:
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<absolute-ordering/>
</web-app>
然后,可以通过名称有选择地启用 Web 片段,例如 Spring 自己的 SpringServletContainerInitializer
,它提供对 Servlet 3 Java 初始化 API 的支持。以下示例显示了如何执行此操作:
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<absolute-ordering>
<name>spring_web</name>
</absolute-ordering>
</web-app>
配置服务器
您可以配置底层 WebSocket 服务器,例如输入消息缓冲区大小、空闲超时等。
对于 Jakarta WebSocket 服务器,您可以在 Java 配置中添加一个 ServletServerContainerFactoryBean
。例如:
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
或者在 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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://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>
对于客户端 Jakarta WebSocket 配置,在 Java 配置中使用 ContainerProvider.getWebSocketContainer(),或在 XML 中使用 WebSocketContainerFactoryBean 。 |
对于 Jetty,您可以提供一个 Consumer
回调来配置 WebSocket 服务器:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
strategy.addWebSocketConfigurer(configurable -> {
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
});
return new DefaultHandshakeHandler(strategy);
}
}
在使用 STOMP over WebSocket 时,您还需要配置 STOMP WebSocket 传输 属性。 |
允许的来源
从 Spring Framework 4.1.5 开始,WebSocket 和 SockJS 的默认行为是仅接受同源请求。也可以允许所有或指定列表的来源。此检查主要设计用于浏览器客户端。没有阻止其他类型的客户端修改 Origin
标头值(有关更多详细信息,请参阅 RFC 6454:Web 来源概念)。
有三种可能的行为:
-
仅允许同源请求(默认):在此模式下,启用 SockJS 时,Iframe HTTP 响应标头
X-Frame-Options
设置为SAMEORIGIN
,并禁用 JSONP 传输,因为它不允许检查请求的来源。因此,在启用此模式时,不支持 IE6 和 IE7。 -
允许指定列表的来源:每个允许的来源必须以
http://
或https://
开头。在此模式下,启用 SockJS 时,禁用 IFrame 传输。因此,在启用此模式时,不支持 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;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
}
@Bean
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers allowed-origins="https://mydomain.com">
<websocket:mapping path="/myHandler" handler="myHandler" />
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>