WebSocket API

Spring 框架提供了一个 WebSocket API,您可以使用它来编写处理 WebSocket 消息的客户端和服务器端应用程序。

WebSocketHandler

创建 WebSocket 服务器与实现 WebSocketHandler 一样简单,或者更可能的是扩展 TextWebSocketHandlerBinaryWebSocketHandler。以下示例使用 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>