Spring Session - WebSocket

本指南說明如何使用 Spring Session 以確保 WebSocket 訊息能保持您的 HttpSession 存活。

Spring Session 的 WebSocket 支援僅適用於 Spring 的 WebSocket 支援。明確地說,它不適用於直接使用 JSR-356,因為 JSR-356 沒有攔截傳入 WebSocket 訊息的機制。

HttpSession 設定

第一步是將 Spring Session 與 HttpSession 整合。這些步驟已在HttpSession with Redis 指南中概述。

在繼續之前,請確保您已將 Spring Session 與 HttpSession 整合。

Spring 配置

在典型的 Spring WebSocket 應用程式中,您會實作 WebSocketMessageBrokerConfigurer。例如,組態可能如下所示

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/messages").withSockJS();
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/queue/", "/topic/");
		registry.setApplicationDestinationPrefixes("/app");
	}

}

我們可以更新組態以使用 Spring Session 的 WebSocket 支援。以下範例示範如何操作

src/main/java/samples/config/WebSocketConfig.java
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { (1)

	@Override
	protected void configureStompEndpoints(StompEndpointRegistry registry) { (2)
		registry.addEndpoint("/messages").withSockJS();
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/queue/", "/topic/");
		registry.setApplicationDestinationPrefixes("/app");
	}

}

為了掛勾 Spring Session 支援,我們只需要變更兩件事

1 我們不實作 WebSocketMessageBrokerConfigurer,而是擴展 AbstractSessionWebSocketMessageBrokerConfigurer
2 我們將 registerStompEndpoints 方法重新命名為 configureStompEndpoints

AbstractSessionWebSocketMessageBrokerConfigurer 在幕後做了什麼?

  • WebSocketConnectHandlerDecoratorFactory 作為 WebSocketHandlerDecoratorFactory 被新增至 WebSocketTransportRegistration。這確保了觸發一個自訂的 SessionConnectEvent,其中包含 WebSocketSession。當 Spring Session 結束時,WebSocketSession 是結束任何仍然開啟的 WebSocket 連線所必需的。

  • SessionRepositoryMessageInterceptor 作為 HandshakeInterceptor 被新增至每個 StompWebSocketEndpointRegistration。這確保了 Session 被新增到 WebSocket 屬性中,以啟用更新上次存取時間。

  • SessionRepositoryMessageInterceptor 作為 ChannelInterceptor 被新增至我們的 inbound ChannelRegistration。這確保了每次收到 inbound 訊息時,都會更新 Spring Session 的上次存取時間。

  • WebSocketRegistryListener 被建立為 Spring bean。這確保了我們擁有所有 Session ID 對應到相應 WebSocket 連線的映射。透過維護此映射,我們可以在 Spring Session (HttpSession) 結束時關閉所有 WebSocket 連線。

websocket 範例應用程式

websocket 範例應用程式示範如何將 Spring Session 與 WebSockets 一起使用。

執行 websocket 範例應用程式

您可以透過取得原始碼並調用以下命令來執行範例

$ ./gradlew :spring-session-sample-boot-websocket:bootRun

為了測試 session 過期,您可能希望在啟動應用程式之前新增以下組態屬性,將 session 過期時間變更為 1 分鐘(預設為 30 分鐘)

src/main/resources/application.properties
server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.
為了使範例運作,您必須在 localhost 上安裝 Redis 2.8+ 並以預設連接埠 (6379) 執行它。或者,您可以更新 RedisConnectionFactory 以指向 Redis 伺服器。另一個選項是使用 Docker 在 localhost 上執行 Redis。請參閱 Docker Redis repository 以取得詳細說明。

現在您應該可以透過 localhost:8080/ 存取應用程式

探索 websocket 範例應用程式

現在您可以嘗試使用此應用程式。使用以下資訊進行身份驗證

  • 使用者名稱 rob

  • 密碼 password

現在點擊 登入 按鈕。您現在應該已通過使用者 rob 的身份驗證。

開啟一個無痕視窗並存取 localhost:8080/

系統會提示您輸入登入表單。使用以下資訊進行身份驗證

  • 使用者名稱 luke

  • 密碼 password

現在從 rob 發送訊息給 luke。訊息應該會出現。

等待兩分鐘,然後再次嘗試從 rob 發送訊息給 luke。您可以看到訊息不再發送。

為什麼是兩分鐘?

Spring Session expires in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds. To ensure the socket is closed in a reasonable amount of time, Spring Session runs a background task every minute at 00 seconds that forcibly cleans up any expired sessions. This means you need to wait at most two minutes before the WebSocket connection is closed.

您現在可以嘗試存取 localhost:8080/。系統會再次提示您進行身份驗證。這證明 session 已正確過期。

現在重複相同的練習,但不要等待兩分鐘,而是每 30 秒從每個使用者發送一則訊息。您可以看到訊息持續發送。嘗試存取 localhost:8080/。系統不會再次提示您進行身份驗證。這證明 session 保持存活。

只有從使用者發送的訊息才能保持 session 存活。這是因為只有來自使用者的訊息才表示使用者活動。收到的訊息並不表示活動,因此不會更新 session 過期時間。