WebSocket Integration
Spring 会话提供与 Spring 的 WebSocket 支持的透明集成。
Spring Session provides transparent integration with Spring’s WebSocket support.
@19
Spring Session - WebSocket
Rob Winch
本指南描述如何使用 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. |
Why Spring Session and WebSockets?
那么当我们使用 WebSocket 时,为什么需要 Spring 会话?
So why do we need Spring Session when we use WebSockets?
请考虑一个通过 HTTP 请求执行大部分工作电子邮件应用程序。然而,它内部还有一个通过 WebSocket API 工作的聊天应用程序。如果用户正在积极地与某人聊天,我们不应该使 HttpSession
超时,因为这会是相当糟糕的用户体验。然而,这正是 JSR-356 所做的。
Consider an email application that does much of its work through HTTP requests.
However, there is also a chat application embedded within it that works over WebSocket APIs.
If a user is actively chatting with someone, we should not timeout the HttpSession
, since this would be a pretty poor user experience.
However, this is exactly what JSR-356 does.
另一个问题是,根据 JSR-356,如果 HttpSession
超时,使用该 HttpSession
创建的任何 WebSocket 都应该被强行关闭。这意味着,如果我们正在应用程序中积极聊天,并且未使用 HttpSession,我们也会断开与对话的连接。
Another issue is that, according to JSR-356, if the HttpSession
times out, any WebSocket that was created with that HttpSession
and an authenticated user should be forcibly closed.
This means that, if we are actively chatting in our application and are not using the HttpSession, we also do disconnect from our conversation.
WebSocket Usage
WebSocket Sample 提供了一个 Spring Session 与 WebSockets 集成的工作示例。您可以按照接下来几小节描述的集成基本步骤,但是我们鼓励您在与自己的应用程序集成时按照详细的 WebSocket 指南进行操作。
The WebSocket Sample provides a working sample of how to integrate Spring Session with WebSockets. You can follow the basic steps for integration described in the next few headings, but we encourage you to follow along with the detailed WebSocket Guide when integrating with your own application.
HttpSession
Integration
在使用 WebSocket 集成之前,您应该确保首先让 HttpSession
Integration 正常工作。
Before using WebSocket integration, you should be sure that you have HttpSession
Integration working first.
@19
Spring Session - WebSocket
Rob Winch
本指南描述如何使用 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. |