Spring Session - WebSocket
本指南描述如何使用 Spring 会话来确保 WebSocket 消息使您的 HttpSession 保持活动状态。
This guide describes how to use Spring Session to ensure that WebSocket messages keep your HttpSession alive.
Spring Session 的 WebSocket 支持仅适用于 Spring 的 WebSocket 支持。具体来说,它不适用于直接使用 JSR-356,因为 JSR-356 没有用于拦截传入 WebSocket 消息的机制。 |
Spring Session’s WebSocket support works only with Spring’s WebSocket support. Specifically,it does not work with using JSR-356 directly, because JSR-356 does not have a mechanism for intercepting incoming WebSocket messages. |
HttpSession Setup
第一步是将 Spring Session 与 HttpSession 集成。这些步骤已在 HttpSession with Redis Guide 中概述。
The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the HttpSession with Redis Guide.
确保在继续之前,您已将 Spring 会话与 HttpSession 集成。
Please make sure you have already integrated Spring Session with HttpSession before proceeding.
Spring Configuration
在典型的 Spring WebSocket 应用程序中,您会实现 WebSocketMessageBrokerConfigurer
。例如,配置可能类似于以下内容:
In a typical Spring WebSocket application, you would implement WebSocketMessageBrokerConfigurer
.
For example, the configuration might look something like the following:
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @author Rob Winch
*/
// tag::class[]
@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");
}
}
// end::class[]
我们可以更新配置以使用 Spring 会话的 WebSocket 支持。以下示例演示如何操作:
We can update our configuration to use Spring Session’s WebSocket support. The following example shows how to do so:
Unresolved include directive in modules/ROOT/pages/guides/boot-websocket.adoc - include::example$spring-session-samples/spring-session-sample-boot-websocket/src/main/java/sample/config/WebSocketConfig.java[]
连接到 Spring 会话支持时,我们只需做两件事:
To hook in the Spring Session support we only need to change two things:
1 | Instead of implementing WebSocketMessageBrokerConfigurer , we extend AbstractSessionWebSocketMessageBrokerConfigurer |
2 | We rename the registerStompEndpoints method to configureStompEndpoints |
`AbstractSessionWebSocketMessageBrokerConfigurer`在幕后执行什么操作?
What does AbstractSessionWebSocketMessageBrokerConfigurer
do behind the scenes?
-
WebSocketConnectHandlerDecoratorFactory
is added as aWebSocketHandlerDecoratorFactory
toWebSocketTransportRegistration
. This ensures a customSessionConnectEvent
is fired that contains theWebSocketSession
. TheWebSocketSession
is necessary to end any WebSocket connections that are still open when a Spring Session is ended. -
SessionRepositoryMessageInterceptor
is added as aHandshakeInterceptor
to everyStompWebSocketEndpointRegistration
. This ensures that theSession
is added to the WebSocket properties to enable updating the last accessed time. -
SessionRepositoryMessageInterceptor
is added as aChannelInterceptor
to our inboundChannelRegistration
. This ensures that every time an inbound message is received, that the last accessed time of our Spring Session is updated. -
WebSocketRegistryListener
is created as a Spring bean. This ensures that we have a mapping of all of theSession
IDs to the corresponding WebSocket connections. By maintaining this mapping, we can close all the WebSocket connections when a Spring Session (HttpSession) is ended.
websocket
Sample Application
`websocket`示例应用程序演示了如何在 WebSocket 中使用 Spring Session。
The websocket
sample application demonstrates how to use Spring Session with WebSockets.
Running the websocket
Sample Application
您可以获取 源代码 并调用以下命令运行示例:
You can run the sample by obtaining the source code and invoking the following command:
$ ./gradlew :spring-session-sample-boot-websocket:bootRun
出于测试会话到期时间的目的,你可能希望在启动应用程序之前添加以下配置属性,将会话到期时间更改为 1 分钟(默认值为 30 分钟): For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (the default is 30 minutes) by adding the following configuration property before starting the application: |
server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.
要让示例发挥作用,你必须在 localhost 上 install Redis 2.8+ 并使用默认端口 (6379) 运行它。或者,你可以更新 |
For the sample to work, you must install Redis 2.8+ on localhost and run it with the default port (6379).
Alternatively, you can update the |
您现在应该能够访问 [role="bare"][role="bare"]http://localhost:8080/ 中的应用程序。
You should now be able to access the application at [role="bare"]http://localhost:8080/
Exploring the websocket
Sample Application
现在你可以尝试使用该应用程序。使用以下信息进行身份验证:
Now you can try using the application. Authenticate with the following information:
-
Username rob
-
Password password
现在,单击*Login*按钮。你现在应该以用户*rob*的身份进行身份验证。
Now click the Login button. You should now be authenticated as the user rob.
打开一个隐身窗口并访问 [role="bare"][role="bare"]http://localhost:8080/
Open an incognito window and access [role="bare"]http://localhost:8080/
系统会提示你提供一个登录表单。使用以下信息进行身份验证:
You are prompted with a login form. Authenticate with the following information:
-
Username luke
-
Password password
现在从 Rob 发送一条消息给 Luke。消息应该会显示出来。
Now send a message from rob to luke. The message should appear.
等待两分钟,然后再次尝试从 Rob 给 Luke 发送一条消息。你可以看到,消息不再发送。
Wait for two minutes and try sending a message from rob to luke again. You can see that the message is no longer sent.
Why two minutes?
Spring Session 在 60 秒后到期,但不能保证在 60 秒内收到来自 Redis 的通知。为了确保在合理的时间范围内关闭套接字,Spring Session 会在每分钟的第 00 秒运行一个后台任务,以强制清除所有过期的会话。这意味着你需要最多等待两分钟,WebSocket 连接才会关闭。 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. |
现在,您可以尝试访问 [role="bare"][role="bare"]http://localhost:8080/,您会收到再次进行身份验证的提示。这表明会话已正确过期。
You can now try accessing [role="bare"]http://localhost:8080/ You are prompted to authenticate again. This demonstrates that the session properly expires.
现在重复相同的练习,但是不要等待两分钟,而是在每 30 秒内从每个用户发送一条消息。您可以看到仍会继续发送消息。尝试访问 [role="bare"][role="bare"]http://localhost:8080/,您不会收到再次进行身份验证的提示。这表明会话保持活跃。
Now repeat the same exercise, but instead of waiting two minutes, send a message from each of the users every 30 seconds. You can see that the messages continue to be sent. Try accessing [role="bare"]http://localhost:8080/ You are not prompted to authenticate again. This demonstrates the session is kept alive.
只有来自用户的消息才能使会话保持活动状态。这是因为只有来自用户的消息才表示用户活动。收到的消息并不表示活动,因此不会续订会话过期。 |
Only messages sent from a user keep the session alive. This is because only messages coming from a user imply user activity. Received messages do not imply activity and, thus, do not renew the session expiration. |