boot-websocket.md 8.4 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
# Spring Session - WebSocket

This guide describes how to use Spring Session to ensure that WebSocket messages keep your HttpSession alive.

|   |Spring Session’s WebSocket support works only with Spring’s WebSocket support.<br/>Specifically,it does not work with using [JSR-356](https://www.jcp.org/en/jsr/detail?id=356) directly, because JSR-356 does not have a mechanism for intercepting incoming WebSocket messages.|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

[Index](../index.html)

## HttpSession Setup

The first step is to integrate Spring Session with the HttpSession. These steps are already outlined in the [HttpSession with Redis Guide](./boot-redis.html).

Please make sure you have already integrated Spring Session with HttpSession before proceeding.

## Spring Configuration

In a typical Spring WebSocket application, you would implement `WebSocketMessageBrokerConfigurer`.
For example, the configuration might look something like the following:

```
@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");
	}

}
```

We can update our configuration to use Spring Session’s WebSocket support.
The following example shows how to do so:

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");
	}

}
```

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

What does `AbstractSessionWebSocketMessageBrokerConfigurer` do behind the scenes?

* `WebSocketConnectHandlerDecoratorFactory` is added as a `WebSocketHandlerDecoratorFactory` to `WebSocketTransportRegistration`.
  This ensures a custom `SessionConnectEvent` is fired that contains the `WebSocketSession`.
  The `WebSocketSession` is necessary to end any WebSocket connections that are still open when a Spring Session is ended.

* `SessionRepositoryMessageInterceptor` is added as a `HandshakeInterceptor` to every `StompWebSocketEndpointRegistration`.
  This ensures that the `Session` is added to the WebSocket properties to enable updating the last accessed time.

* `SessionRepositoryMessageInterceptor` is added as a `ChannelInterceptor` to our inbound `ChannelRegistration`.
  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 the `Session` 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

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](https://github.com/spring-projects/spring-session/archive/main.zip) and invoking the following command:

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

|   |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:<br/><br/>src/main/resources/application.properties<br/><br/>```<br/>server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.<br/>```|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |For the sample to work, you must [install Redis 2.8+](https://redis.io/download) on localhost and run it with the default port (6379).<br/>Alternatively, you can update the `RedisConnectionFactory` to point to a Redis server.<br/>Another option is to use [Docker](https://www.docker.com/) to run Redis on localhost.<br/>See [Docker Redis repository](https://hub.docker.com/_/redis/) for detailed instructions.|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

You should now be able to access the application at [http://localhost:8080/](http://localhost:8080/)

### Exploring the `websocket` Sample Application

Now you can try using the application. Authenticate with the following information:

* **Username** *rob*

* **Password** *password*

Now click the **Login** button. You should now be authenticated as the user **rob**.

Open an incognito window and access [http://localhost:8080/](http://localhost:8080/)

You are prompted with a login form. Authenticate with the following information:

* **Username** *luke*

* **Password** *password*

Now send a message from rob to luke. The message should appear.

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?<br/><br/>Spring Session expires in 60 seconds, but the notification from Redis is not guaranteed to happen within 60 seconds.<br/>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.<br/>This means you need to wait at most two minutes before the WebSocket connection is closed.|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

You can now try accessing [http://localhost:8080/](http://localhost:8080/)You are prompted to authenticate again.
This demonstrates that the session properly expires.

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 [http://localhost:8080/](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.<br/>This is because only messages coming from a user imply user activity.<br/>Received messages do not imply activity and, thus, do not renew the session expiration.|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|