Как обновить http-сессию активности веб-сокета, чтобы не отключать время ожидания активных пользователей?JAVA

Программисты JAVA общаются здесь
Ответить Пред. темаСлед. тема
Anonymous
 Как обновить http-сессию активности веб-сокета, чтобы не отключать время ожидания активных пользователей?

Сообщение Anonymous »

Я так долго пользуюсь этим форумом, и наконец пришло время задать мой первый вопрос. :)
Я разрабатываю многопользовательскую игру как личный проект (приложение Spring Boot + React). Большая часть активности пользователя в игре происходит через веб-сокет, поэтому мне нужно, чтобы сеанс Spring сохранялся до тех пор, пока существует какая-либо активность веб-сокета.
Я выполнил конфигурацию, описанную в разделе документация:
https://docs.spring.io/spring-session/r ... ocket.html
и там сказано, что Spring должен автоматически обрабатывать обновление сеанса - именно то, что я хочу, но это просто не работает. Сообщения веб-сокета не обновляют весенний сеанс, что приводит к тайм-ауту.
Вот мой класс конфигурации веб-сокета:

Код: Выделить всё

package com.myapp.guess_who.configuration;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.session.Session;
import org.springframework.session.web.socket.config.annotation.AbstractSessionWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@RequiredArgsConstructor
@EnableWebSocketMessageBroker
@Configuration
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer  {

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

@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("http://localhost:3000");
}
}

Настройки весеннего сеанса в application.yaml:

Код: Выделить всё

spring:
session:
timeout: 15s
redis:
repository-type: indexed # needed to be able to listen for redis session events
и класс, прослушивающий события создания и уничтожения сеанса, чтобы распечатать некоторую информацию и тайм-аут пользователя:

Код: Выделить всё

package com.myapp.guess_who.listener;

import com.myapp.guess_who.room.RoomManager;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.session.Session;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

@RequiredArgsConstructor
@Component
public class RedisSessionListener {

private final SimpMessagingTemplate messagingTemplate;
private final RoomManager roomManager;

@EventListener
public void sessionCreated(SessionCreatedEvent event) {
System.out.printf("Session %s created%n", event.getSession().getId());
System.out.printf("%s - creation time%n%n", event.getSession().getCreationTime().atZone(ZoneId.systemDefault()).toLocalTime());
}

@EventListener
public void sessionDestroyed(SessionDestroyedEvent event) {
System.out.printf("Session %s destroyed%n", event.getSession().getId());
System.out.printf("%s - creation time%n", event.getSession().getCreationTime().atZone(ZoneId.systemDefault()).toLocalTime());
System.out.printf("%s - last accessed time%n", event.getSession().getLastAccessedTime().atZone(ZoneId.systemDefault()).toLocalTime());
System.out.printf("%s - current time%n%n", Instant.now().truncatedTo(ChronoUnit.MILLIS).atZone(ZoneId.systemDefault()).toLocalTime());

Session session = event.getSession();
UUID roomId = session.getAttribute("roomId");
UUID playerId = session.getAttribute("playerId");

if (roomId == null || playerId == null || !roomManager.roomExists(roomId)) {
return;
}

roomManager.removePlayer(roomId, playerId);
messagingTemplate.convertAndSend("/topic/room/%s/player/%s/sessionInvalidate".formatted(roomId, playerId), "timeout");
}
}

Если будет полезно, вот код для смены команды (остальные методы класса я вырезал — они не нужны для моего объяснения):

Код: Выделить всё

package com.myapp.guess_who.player;

import com.myapp.guess_who.room.RoomManager;
import com.myapp.guess_who.team.Team;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import java.time.Instant;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;

@Slf4j
@RequiredArgsConstructor
@Controller
public class PlayerController {

private final RoomManager roomManager;
private final PlayerService playerService;

@MessageMapping("/room/{roomId}/player/{playerId}/changeTeam")
@SendTo("/topic/room/{roomId}/players")
public Map changePlayerTeam(
@DestinationVariable("roomId") UUID roomId,
@DestinationVariable("playerId") UUID playerId,
@Payload Team newTeam
) {
Map  players = roomManager.getRoom(roomId).getPlayers();
System.out.printf(
"%s - changePlayerTeam called%n%n",
Instant.now().truncatedTo(ChronoUnit.MILLIS).atZone(ZoneId.systemDefault()).toLocalTime()
);
playerService.changePlayerTeam(players, playerId, newTeam);
return players;
}
}

Приложение работает нормально, таймаут через 15 секунд работает нормально. Сеанс, очевидно, продлевается с каждым HTTP-запросом, НО не с сообщением веб-сокета.
Постоянная смена команды во время ожидания в комнате не возобновляет сеанс и приводит к тайм-ауту через 15 секунд. Вот вывод консоли:

Код: Выделить всё

Session 428df918-f84a-4203-b9c6-c9fc63e9eed9 created
17:15:05.029 - creation time

17:15:14.978 - changePlayerTeam called

17:15:17.884 - changePlayerTeam called

Session 428df918-f84a-4203-b9c6-c9fc63e9eed9 destroyed
17:15:05.029 - creation time
17:15:05.549 - last accessed time
17:15:20.605 - current time
Я еще не развернул приложение, поэтому все происходит в моей локальной среде, а это означает, что сервер работает на Tomcat.
Я попробовал обновить весеннюю сессию вручную в моем специальном перехватчике:

Код: Выделить всё

package com.myapp.guess_who.interceptor;

import jakarta.servlet.http.HttpSession;
import org.springframework.lang.NonNull;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Component
public class WebSocketSessionInterceptor implements ChannelInterceptor {

@Override
public Message preSend(@NonNull Message message, @NonNull MessageChannel channel) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message);
System.out.println(headerAccessor.getSessionAttributes());
// Access HttpSession from the message headers
HttpSession httpSession = (HttpSession) Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("HTTP_SESSION");

if (httpSession != null) {
// Manually update the session to renew it
httpSession.setAttribute("lastAccessedTime", System.currentTimeMillis());
}

return message;
}
}

который я зарегистрировал в классе WebSocketConfig:

Код: Выделить всё

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketSessionInterceptor);
}
но строка:

Код: Выделить всё

System.out.println(headerAccessor.getSessionAttributes());
печатает пустую карту:
{}
Похоже, что автоконфигурация Spring неправильно сопоставляет сеанс http с сеансом веб-сокета.
Я искал решение на форумах и в документации уже 2 дня, но не нашел ни одного, которое бы работало. Я что-то пропустил?
Я знаю, что могу реализовать это вручную, но мне бы очень хотелось знать, как заставить его работать с механизмом автоконфигурации Spring и избежать ненужного кода.

Подробнее здесь: https://stackoverflow.com/questions/791 ... tive-users
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «JAVA»