WebSocket API

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

WebSocketHandler

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

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

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

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 を継承することです。これには、クライアントの発信元の検証、サブプロトコルのネゴシエーション、その他の詳細が含まれます。アプリケーションは、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 サーバーの場合は、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);
	}

}
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 が許可するオリジンを設定できます。

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>