对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

WebSocket API

Spring框架提供了一个WebSocket API,你可以使用它来编写处理WebSocket消息的客户端和服务器端应用程序。spring-doc.cadn.net.cn

WebSocketHandler

创建WebSocket服务器就像实现WebSocketHandler一样简单,或者更可能的是,扩展TextWebSocketHandlerBinaryWebSocketHandler。以下示例使用TextWebSocketHandlerspring-doc.cadn.net.cn

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处理器映射到特定URL,有专门的WebSocket Java配置和XML命名空间支持,如下例所示:spring-doc.cadn.net.cn

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配置等价写法:spring-doc.cadn.net.cn

<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服务环境中。spring-doc.cadn.net.cn

在直接使用WebSocketHandler API与间接使用,例如通过STOMP消息传递时,应用程序必须同步消息的发送,因为底层的标准WebSocket会话(JSR-356)不允许并发发送。一个选项是将WebSocketSession包装为ConcurrentWebSocketSessionDecoratorspring-doc.cadn.net.cn

WebSocket握手

定制初始HTTP WebSocket握手请求的最简单方法是通过一个HandshakeInterceptor,它提供了在握手“之前”和“之后”的方法。你可以使用此类拦截器来阻止握手或使任何属性对WebSocketSession可用。以下示例使用了一个内置拦截器将HTTP会话属性传递给WebSocket会话:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(new MyHandler(), "/myHandler")
			.addInterceptors(new HttpSessionHandshakeInterceptor());
	}

}

以下示例展示了前面示例的XML配置等价写法:spring-doc.cadn.net.cn

<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命名空间都允许配置自定义HandshakeHandlerspring-doc.cadn.net.cn

Spring 提供了一个 WebSocketHandlerDecorator 基类,你可以用来装饰一个 WebSocketHandler 以添加额外的行为。日志记录和异常处理实现已经提供,并且在使用 WebSocket Java 配置或 XML 命名空间时默认添加。ExceptionWebSocketHandlerDecorator 捕获任何 WebSocketHandler 方法中出现的所有未捕获的异常,并使用状态 1011 关闭 WebSocket 会话,该状态表示服务器错误。

部署

Spring WebSocket API 很容易集成到 Spring MVC 应用程序中,在那里 DispatcherServlet 同时处理 HTTP WebSocket 握手和其他 HTTP 请求。它也很容易通过调用 WebSocketHttpRequestHandler 集成到其他 HTTP 处理场景中。这很方便且易于理解。然而,对于 JSR-356 运行时有一些特殊注意事项。spring-doc.cadn.net.cn

雅加达WebSocket API(JSR-356)提供两种部署机制。第一种机制涉及在启动时进行Servlet容器类路径扫描(Servlet 3的特性)。另一种是在Servlet容器初始化时使用的注册API。这两种机制都无法实现为所有HTTP处理(包括WebSocket握手和其他所有HTTP请求)使用单一"前端控制器",例如Spring MVC的DispatcherServletspring-doc.cadn.net.cn

这是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)上选择使用标准请求升级策略。spring-doc.cadn.net.cn

一个次要的考虑因素是,支持JSR-356的Servlet容器预计会执行ServletContainerInitializer(SCI)扫描,这可能会减慢应用程序启动速度——在某些情况下,这种影响非常明显。如果在升级到支持JSR-356的Servlet容器版本后观察到了显著的影响,应该可以通过在web.xml中使用<absolute-ordering />元素来选择性地启用或禁用Web片段(和SCI扫描),如下例所示:spring-doc.cadn.net.cn

<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的支持。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<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服务器的各项参数,例如输入消息缓冲区大小、空闲超时时间等。spring-doc.cadn.net.cn

对于Jakarta WebSocket服务器,您可以在Java配置中添加一个ServletServerContainerFactoryBean。例如:spring-doc.cadn.net.cn

@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
    ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
    container.setMaxTextMessageBufferSize(8192);
    container.setMaxBinaryMessageBufferSize(8192);
    return container;
}

或者您也可以将其应用于XML配置:spring-doc.cadn.net.cn

<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服务器:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(echoWebSocketHandler(),
			"/echo").setHandshakeHandler(handshakeHandler());
	}

	@Bean
	public DefaultHandshakeHandler handshakeHandler() {
		WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
		policy.setInputBufferSize(8192);
		policy.setIdleTimeout(600000);
		return new DefaultHandshakeHandler(
				new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
	}

}

以下示例展示了前面示例的XML配置等价写法:spring-doc.cadn.net.cn

<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="/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>
在使用WebSocket上的STOMP时,您还需要配置 STOMP WebSocket传输 属性。

允许的源

从Spring Framework 4.1.5开始,默认行为是仅接受同源请求的WebSocket和SockJS。也可以允许所有或指定列表中的源。此检查主要是为浏览器客户端设计的。没有任何东西阻止其他类型的客户端修改Origin头值(有关详细信息,请参阅RFC 6454: Web Origin Concept)。spring-doc.cadn.net.cn

三种可能的行为是:spring-doc.cadn.net.cn

  • 仅允许同源请求(默认):在这种模式下,当SockJS启用时,Iframe HTTP响应头X-Frame-Options被设置为SAMEORIGIN,并且禁用JSONP传输,因为它不允许检查请求的来源。因此,当启用此模式时,IE6和IE7不受支持。spring-doc.cadn.net.cn

  • 允许指定的来源列表:每个允许的来源必须以 http://https:// 开头。在这种模式下,当 SockJS 启用时,IFrame 传输将被禁用。因此,启用此模式时,IE6 到 IE9 不受支持。spring-doc.cadn.net.cn

  • 允许所有源:要启用此模式,您应提供 * 作为允许的源值。在此模式下,所有传输方式都可用。spring-doc.cadn.net.cn

您可以配置WebSocket和SockJS允许的源,如下例所示:spring-doc.cadn.net.cn

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配置等价写法:spring-doc.cadn.net.cn

<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>