WebSocket API

Spring Framework は、WebSocket メッセージを処理するクライアント側およびサーバー側のアプリケーションを作成するために使用できる WebSocket API を提供します。

WebSocketHandler

WebSocket サーバーの作成は、WebSocketHandler を実装するのと同じくらい簡単で、おそらくは TextWebSocketHandler または BinaryWebSocketHandler のいずれかを継承します。次の例では、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 ハンドラーを特定の URL にマッピングするための専用の WebSocket プログラム構成と XML 名前空間サポートがあります。

  • 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 に依存しません。WebSocketHttpRequestHandler (Javadoc) の助けを借りて、WebSocketHandler を他の HTTP サービス環境に統合するのは比較的簡単です。

WebSocketHandler API を直接使用する場合と、たとえば STOMP メッセージングを介して間接的に使用する場合、基礎となる標準 WebSocket セッション (JSR-356) では同時送信が許可されていないため、アプリケーションはメッセージの送信を同期する必要があります。1 つのオプションは、WebSocketSession を ConcurrentWebSocketSessionDecorator (Javadoc) でラップすることです。

WebSocket ハンドシェイク

最初の HTTP WebSocket ハンドシェイクリクエストをカスタマイズする最も簡単な方法は、ハンドシェイクの「前」と「後」のメソッドを公開する HandshakeInterceptor を使用することです。このようなインターセプターを使用して、ハンドシェイクを除外したり、WebSocketSession で属性を使用可能にしたりできます。次の例では、組み込みインターセプターを使用して、HTTP セッション属性を WebSocket セッションに渡します。

  • 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 を継承することです。これには、クライアントの発信元の検証、サブプロトコルのネゴシエーション、その他の詳細が含まれます。アプリケーションは、WebSocket サーバーエンジンとまだサポートされていないバージョンに適応するためにカスタム RequestUpgradeStrategy を構成する必要がある場合、このオプションを使用する必要があります(このテーマの詳細については、デプロイを参照してください)。Java 構成と XML 名前空間の両方により、カスタム HandshakeHandler を構成できます。

Spring は、WebSocketHandler を追加の動作で装飾するために使用できる WebSocketHandlerDecorator 基本クラスを提供します。WebSocket Java 構成または XML 名前空間を使用する場合、デフォルトでロギングおよび例外処理の実装が提供および追加されます。ExceptionWebSocketHandlerDecorator は、WebSocketHandler メソッドから発生するすべてのキャッチされない例外をキャッチし、サーバーエラーを示すステータス 1011 の WebSocket セッションを閉じます。

デプロイ

Spring WebSocket API は、DispatcherServlet が HTTP WebSocket ハンドシェイクと他の HTTP リクエストの両方に対応する Spring MVC アプリケーションに簡単に統合できます。WebSocketHttpRequestHandler を呼び出すことで、他の HTTP 処理シナリオに簡単に統合することもできます。これは便利で分かりやすいです。ただし、JSR-356 ランタイムに関しては特別な考慮事項が適用されます。

Jakarta WebSocket API (JSR-356) は、2 つの デプロイメカニズムを提供します。1 つ目は、起動時のサーブレットコンテナークラスパススキャン (Servlet 3 機能) です。もう 1 つは、サーブレットコンテナーの初期化時に使用する登録 API です。これらのメカニズムのどちらも、WebSocket ハンドシェイクや Spring MVC の DispatcherServlet などの他のすべての HTTP リクエストを含む、すべての HTTP 処理に単一の「フロントコントローラー」を使用することを可能にします。

これは、JSR-356 ランタイムで実行されている場合でも、Spring の WebSocket がサーバー固有の RequestUpgradeStrategy 実装でアドレスをサポートするという JSR-356 の重大な制限です。このような戦略は現在、Tomcat、Jetty、GlassFish、WebLogic、WebSphere、Undertow (および WildFly) に存在します。Jakarta WebSocket 2.1 の時点で、Spring が Tomcat 10.1 や Jetty 12 などの Jakarta EE 10 ベースの Web コンテナーで選択する標準のリクエストアップグレード戦略が利用可能です。

2 番目の考慮事項は、JSR-356 をサポートするサーブレットコンテナーが ServletContainerInitializer (SCI)スキャンを実行すると予想されることです。これにより、アプリケーションの起動が遅くなる場合があります。JSR-356 をサポートするサーブレットコンテナーバージョンへのアップグレード後に重大な影響が観察された場合、次の例のように、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>

次に、Servlet 3 Java 初期化 API のサポートを提供する Spring 独自の SpringServletContainerInitializer など、名前で Web フラグメントを選択的に有効にできます。次の例は、その方法を示しています。

<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 サーバーの場合、構成に ServletServerContainerFactoryBean を追加できます。例:

  • 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 を使用します。

Jetty の場合、WebSocket サーバーを構成するためのコールバックを提供できます。

  • 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 トランスポートプロパティも構成する必要があります。

許可されたオリジン

Spring Framework 4.1.5 以降、WebSocket および SockJS のデフォルトの動作では、同一生成元のリクエストのみを受け入れます。すべてまたは指定されたオリジンのリストを許可することもできます。このチェックは、主にブラウザークライアント用に設計されています。他の型のクライアントが Origin ヘッダー値を変更することを妨げるものはありません(詳細については、RFC 6454: Web Origin のコンセプト [IETF] (英語) を参照してください)。

3 つの可能な動作は次のとおりです。

  • 同一オリジンリクエストのみを許可(デフォルト): このモードでは、SockJS が有効になっている場合、リクエストのオリジンのチェックが許可されないため、Iframe HTTP レスポンスヘッダー X-Frame-Options が SAMEORIGIN に設定され、JSONP トランスポートが無効になります。そのため、このモードが有効になっている場合、IE6 および IE7 はサポートされません。

  • 指定されたオリジンのリストを許可: 許可された各オリジンは http:// または https:// で始まる必要があります。このモードでは、SockJS を有効にすると、IFrame トランスポートが無効になります。そのため、このモードが有効になっている場合、IE6 から IE9 はサポートされません。

  • すべてのオリジンを許可: このモードを有効にするには、許可された起点値として * を提供する必要があります。このモードでは、すべてのトランスポートが利用可能です。

次の例に示すように、WebSocket と SockJS が許可するオリジンを設定できます。

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