WebSocket API

Spring Framework提供了一个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>

更高级的选项是扩展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>