WebSocket API

Spring Framework 提供了 WebSocket API,您可以使用该 API 编写处理 WebSocket 消息的客户端和服务端应用程序。

The Spring Framework provides a WebSocket API that you can use to write client- and server-side applications that handle WebSocket messages.

WebSocketHandler

创建 WebSocket 服务器与实现 WebSocketHandler 一样简单,或更可能的情况是扩展 TextWebSocketHandlerBinaryWebSocketHandler。以下示例使用 TextWebSocketHandler

Creating a WebSocket server is as simple as implementing WebSocketHandler or, more likely, extending either TextWebSocketHandler or BinaryWebSocketHandler. The following example uses TextWebSocketHandler:

  • Java

  • Kotlin

public class MyHandler extends TextWebSocketHandler {

	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) {
		// ...
	}
}
class MyHandler : TextWebSocketHandler() {

	override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
		// ...
	}
}

有专门的 WebSocket 编程配置和 XML 命名空间支持,用于将前面的 WebSocket 处理程序映射到特定 URL,如下例所示:

There is dedicated WebSocket programmatic configuration and XML namespace support for mapping the preceding WebSocket handler to a specific URL, as the following example shows:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {

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

	@Bean
	public WebSocketHandler myHandler() {
		return new MyHandler();
	}
}
@Configuration
@EnableWebSocket
class WebSocketConfiguration : WebSocketConfigurer {

	override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
		registry.addHandler(myHandler(), "/myHandler")
	}

	@Bean
	fun myHandler(): WebSocketHandler {
		return MyHandler()
	}
}
<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.docs.web.websocket.websocketserverhandler.MyHandler"/>

</beans>

前面的示例用于 Spring MVC 应用程序,并且应该包含在 DispatcherServlet的配置中。然而,Spring 的 WebSocket 支持并不依赖于 Spring MVC。在https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.html[WebSocketHttpRequestHandler]的帮助下,将 `WebSocketHandler`集成到其他 HTTP 服务环境中相对简单。

The preceding example is for use in Spring MVC applications and should be included in the configuration of a DispatcherServlet. However, Spring’s WebSocket support does not depend on Spring MVC. It is relatively simple to integrate a WebSocketHandler into other HTTP-serving environments with the help of WebSocketHttpRequestHandler.

在直接对 WebSocketHandler`API 使用或间接(例如,通过STOMP消息传递)使用时,应用程序必须同步发送消息,因为底层标准 WebSocket 会话 (JSR-356) 不允许并发发送。其中一种选择是使用https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/handler/ConcurrentWebSocketSessionDecorator.html[`ConcurrentWebSocketSessionDecorator]包装 WebSocketSession

When using the WebSocketHandler API directly vs indirectly, e.g. through the STOMP messaging, the application must synchronize the sending of messages since the underlying standard WebSocket session (JSR-356) does not allow concurrent sending. One option is to wrap the WebSocketSession with ConcurrentWebSocketSessionDecorator.

WebSocket Handshake

自定义初始 HTTP WebSocket 握手请求最简单的方法是通过 HandshakeInterceptor,该拦截器会公开握手中的“before”和“after”方法。你可以使用这样的拦截器来阻止握手或向 WebSocketSession 提供任何属性。以下示例使用内置拦截器将 HTTP 会话属性传递到 WebSocket 会话:

The easiest way to customize the initial HTTP WebSocket handshake request is through a HandshakeInterceptor, which exposes methods for “before” and “after” the handshake. You can use such an interceptor to preclude the handshake or to make any attributes available to the WebSocketSession. The following example uses a built-in interceptor to pass HTTP session attributes to the WebSocket session:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {

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

}
@Configuration
@EnableWebSocket
class WebSocketConfiguration : WebSocketConfigurer {

	override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
		registry.addHandler(MyHandler(), "/myHandler")
			.addInterceptors(HttpSessionHandshakeInterceptor())
	}
}
<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.docs.web.websocket.websocketserverhandler.MyHandler"/>

</beans>

更高级的选项是扩展执行 WebSocket 握手步骤的`DefaultHandshakeHandler`,包括验证客户端原点、协商子协议和其他详细信息。应用程序也可能需要使用此选项,如果它需要配置自定义`RequestUpgradeStrategy`,以适应尚未受支持的 WebSocket 服务器引擎和版本(有关此问题的更多信息,请参见Deployment)。Java 配置和 XML 命名空间都可以配置自定义`HandshakeHandler`。

A more advanced option is to extend the DefaultHandshakeHandler that performs the steps of the WebSocket handshake, including validating the client origin, negotiating a sub-protocol, and other details. An application may also need to use this option if it needs to configure a custom RequestUpgradeStrategy in order to adapt to a WebSocket server engine and version that is not yet supported (see Deployment for more on this subject). Both the Java configuration and XML namespace make it possible to configure a custom HandshakeHandler.

Spring 提供了一个 WebSocketHandlerDecorator 基本类,您可使用该类为 WebSocketHandler 添加其他行为。在使用 WebSocket Java 配置或 XML 命名空间时,会提供并默认添加日志记录和异常处理实现。ExceptionWebSocketHandlerDecorator 会捕获由任何 WebSocketHandler 方法引发的所有未捕获异常,并关闭具有状态 1011 的 WebSocket 会话,该状态表示服务器错误。

Spring provides a WebSocketHandlerDecorator base class that you can use to decorate a WebSocketHandler with additional behavior. Logging and exception handling implementations are provided and added by default when using the WebSocket Java configuration or XML namespace. The ExceptionWebSocketHandlerDecorator catches all uncaught exceptions that arise from any WebSocketHandler method and closes the WebSocket session with status 1011, which indicates a server error.

Deployment

Spring WebSocket API 很容易集成到 DispatcherServlet 同时提供 HTTP WebSocket 握手及其他 HTTP 请求的 Spring MVC 应用程序中。通过调用 WebSocketHttpRequestHandler,也很容易集成到其他 HTTP 处理场景中。这很方便且易于理解。但是,对于 JSR-356 运行时,需要特别考虑。

The Spring WebSocket API is easy to integrate into a Spring MVC application where the DispatcherServlet serves both HTTP WebSocket handshake and other HTTP requests. It is also easy to integrate into other HTTP processing scenarios by invoking WebSocketHttpRequestHandler. This is convenient and easy to understand. However, special considerations apply with regards to JSR-356 runtimes.

Jakarta WebSocket API (JSR-356) 提供两种部署机制。第一个涉及在启动时进行 Servlet 容器类路径扫描(Servlet 3 功能)。另一个是在 Servlet 容器初始化时使用的注册 API。这两种机制都不可能为所有 HTTP 处理(包括 WebSocket 握手和所有其他 HTTP 请求)使用一个“前端控制器”,例如 Spring MVC 的 DispatcherServlet

The Jakarta WebSocket API (JSR-356) provides two deployment mechanisms. The first involves a Servlet container classpath scan (a Servlet 3 feature) at startup. The other is a registration API to use at Servlet container initialization. Neither of these mechanism makes it possible to use a single “front controller” for all HTTP processing — including WebSocket handshake and all other HTTP requests — such as Spring MVC’s DispatcherServlet.

这是 JSR-356 的一个重大限制,即使在 JSR-356 运行时中运行,Spring 的 WebSocket 支持也通过特定于服务器的 RequestUpgradeStrategy 实现解决了这个限制。目前针对 Tomcat、Jetty、GlassFish、WebLogic、WebSphere 和 Undertow(和 WildFly)有此类策略。从 Jakarta WebSocket 2.1 开始,有标准的请求升级策略可用,Spring 会在基于 Jakarta EE 10 的 Web 容器(例如 Tomcat 10.1 和 Jetty 12)上选择该策略。

This is a significant limitation of JSR-356 that Spring’s WebSocket support addresses with server-specific RequestUpgradeStrategy implementations even when running in a JSR-356 runtime. Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and Undertow (and WildFly). As of Jakarta WebSocket 2.1, a standard request upgrade strategy is available which Spring chooses on Jakarta EE 10 based web containers such as Tomcat 10.1 and Jetty 12.

第二个考虑因素是,支持 JSR-356 的 Servlet 容器有望执行 ServletContainerInitializer (SCI) 扫描,这可能会显着减慢应用程序启动速度——在某些情况下,是大幅减慢。如果在升级到支持 JSR-356 的 Servlet 容器版本后观察到重大影响,则应该可以通过在 web.xml 中使用 <absolute-ordering /> 元素来选择性地启用或禁用 Web 片段(和 SCI 扫描),如下例所示:

A secondary consideration is that Servlet containers with JSR-356 support are expected to perform a ServletContainerInitializer (SCI) scan that can slow down application startup — in some cases, dramatically. If a significant impact is observed after an upgrade to a Servlet container version with JSR-356 support, it should be possible to selectively enable or disable web fragments (and SCI scanning) through the use of the <absolute-ordering /> element in web.xml, as the following example shows:

<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 的支持。以下示例展示了如何操作:

You can then selectively enable web fragments by name, such as Spring’s own SpringServletContainerInitializer that provides support for the Servlet 3 Java initialization API. The following example shows how to do so:

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

Configuring the Server

你可以配置底层 WebSocket 服务器,例如输入消息缓冲区大小、空闲超时等。

You can configure of the underlying WebSocket server such as input message buffer size, idle timeout, and more.

对于 Jakarta WebSocket 服务器,你可以向你的配置添加一个 ServletServerContainerFactoryBean。例如:

For Jakarta WebSocket servers, you can add a ServletServerContainerFactoryBean to your configuration. For example:

  • Java

  • Kotlin

  • Xml

@Configuration
public class WebSocketConfiguration {

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

	@Bean
	fun createWebSocketContainer() = ServletServerContainerFactoryBean().apply {
		maxTextMessageBufferSize = 8192
		maxBinaryMessageBufferSize = 8192
	}
}
<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">
	<property name="maxTextMessageBufferSize" value="8192"/>
	<property name="maxBinaryMessageBufferSize" value="8192"/>
</bean>

对于客户端 Jakarta WebSocket 配置,请在以编程方式配置时使用 ContainerProvider.getWebSocketContainer(),或在 XML 中使用 WebSocketContainerFactoryBean

For client Jakarta WebSocket configuration, use ContainerProvider.getWebSocketContainer() in programmatic configuration, or WebSocketContainerFactoryBean in XML.

对于 Jetty,你可以提供一个回调来配置 WebSocket 服务器:

For Jetty, you can supply a callback to configure the WebSocket server:

  • Java

  • Kotlin

@Configuration
@EnableWebSocket
public class JettyWebSocketConfiguration implements WebSocketConfigurer {

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

	@Bean
	public WebSocketHandler echoWebSocketHandler() {
		return new MyEchoHandler();
	}

	@Bean
	public DefaultHandshakeHandler handshakeHandler() {
		JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
		strategy.addWebSocketConfigurer(configurable -> {
			configurable.setInputBufferSize(8192);
			configurable.setIdleTimeout(Duration.ofSeconds(600));
		});
		return new DefaultHandshakeHandler(strategy);
	}
}
@Configuration
@EnableWebSocket
class JettyWebSocketConfiguration : WebSocketConfigurer {

	override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
		registry.addHandler(echoWebSocketHandler(), "/echo").setHandshakeHandler(handshakeHandler())
	}

	@Bean
	fun echoWebSocketHandler(): WebSocketHandler {
		return MyEchoHandler()
	}

	@Bean
	fun handshakeHandler(): DefaultHandshakeHandler {
		val strategy = JettyRequestUpgradeStrategy()
		strategy.addWebSocketConfigurer {
			it.inputBufferSize = 8192
			it.idleTimeout = Duration.ofSeconds(600)
		}
		return DefaultHandshakeHandler(strategy)
	}
}

当在 WebSocket 上使用 STOMP 时,还需要配置 STOMP WebSocket transport 属性。

When using STOMP over WebSocket, you will also need to configure STOMP WebSocket transport properties.

Allowed Origins

在 Spring Framework 4.1.5 中,WebSocket 和 SockJS 的默认行为仅接受同源请求。也可允许全部来源或者特定来源列表。此项检查主要针对浏览器客户端设计。没有任何内容能够阻止其它类型的客户端修改 `Origin`头值(请参阅https://datatracker.ietf.org/doc/html/rfc6454[RFC 6454: Web 源概念]了解更多详细信息)。

As of Spring Framework 4.1.5, the default behavior for WebSocket and SockJS is to accept only same-origin requests. It is also possible to allow all or a specified list of origins. This check is mostly designed for browser clients. Nothing prevents other types of clients from modifying the Origin header value (see RFC 6454: The Web Origin Concept for more details).

三种可能的现象:

The three possible behaviors are:

  • Allow only same-origin requests (default): In this mode, when SockJS is enabled, the Iframe HTTP response header X-Frame-Options is set to SAMEORIGIN, and JSONP transport is disabled, since it does not allow checking the origin of a request. As a consequence, IE6 and IE7 are not supported when this mode is enabled.

  • Allow a specified list of origins: Each allowed origin must start with http:// or https://. In this mode, when SockJS is enabled, IFrame transport is disabled. As a consequence, IE6 through IE9 are not supported when this mode is enabled.

  • Allow all origins: To enable this mode, you should provide * as the allowed origin value. In this mode, all transports are available.

您可以配置 WebSocket 和 SockJS 允许的来源,如下面的示例所示:

You can configure WebSocket and SockJS allowed origins, as the following example shows:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
	}

	@Bean
	public WebSocketHandler myHandler() {
		return new MyHandler();
	}
}
@Configuration
@EnableWebSocket
class WebSocketConfiguration : WebSocketConfigurer {

	override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
		registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com")
	}

	@Bean
	fun myHandler(): WebSocketHandler {
		return MyHandler()
	}
}
<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.docs.web.websocket.websocketserverhandler.MyHandler" />

</beans>