WebSocket API
Spring 框架提供了一个 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>
更高级的选择是扩展执行 WebSocket 握手步骤的 DefaultHandshakeHandler
,包括验证客户端来源、协商子协议以及其他详细信息。如果应用程序需要配置自定义 RequestUpgradeStrategy
以适应尚未支持的 WebSocket 服务器引擎和版本,则可能还需要使用此选项(有关此主题的更多信息,请参见 部署)。Java 配置和 XML 命名空间都允许配置自定义 HandshakeHandler
。
Spring 提供了一个 WebSocketHandlerDecorator 基类,您可以使用它来装饰 WebSocketHandler ,以添加其他行为。日志记录和异常处理实现是提供的,并且在使用 WebSocket Java 配置或 XML 命名空间时默认添加。ExceptionWebSocketHandlerDecorator 会捕获从任何 WebSocketHandler 方法引发的所有未捕获异常,并使用状态 1011 关闭 WebSocket 会话,这表示服务器错误。
|
部署
Spring WebSocket API 很容易集成到 Spring MVC 应用程序中,其中 DispatcherServlet
同时服务于 HTTP WebSocket 握手和其他 HTTP 请求。它也很容易集成到其他 HTTP 处理场景中,方法是调用 WebSocketHttpRequestHandler
。这很方便且易于理解。但是,关于 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 容器版本后观察到重大影响,则可以通过使用 <absolute-ordering />
元素在 web.xml
中选择性地启用或禁用 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>