Статьи

Приложение для загрузки веб-сокетов на основе Spring и получение идентификатора http сессии

Недавно я участвовал в проекте, где нам нужно было захватить идентификатор сеанса http для запроса websocket — причина была в том, чтобы определить количество сеансов websocket, использующих один и тот же базовый сеанс http.

Способ сделать это основан на примере использования нового модуля весенней сессии и описан здесь .

Хитрость в захвате идентификатора http-сессии заключается в том, чтобы понять, что перед установкой соединения веб-сокета между браузером и сервером существует фаза рукопожатия, согласованная по протоколу http, и идентификатор сеанса передается на сервер во время этой фазы квитирования.

Поддержка Spring Websocket предоставляет хороший способ зарегистрировать HandShakeInterceptor , который можно использовать для захвата идентификатора сеанса http и установки его в заголовках суб-протокола (обычно STOMP). Во-первых, это способ получить идентификатор сеанса и установить его в заголовок:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class HttpSessionIdHandshakeInterceptor implements HandshakeInterceptor {
 
 @Override
 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
  if (request instanceof ServletServerHttpRequest) {
   ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
   HttpSession session = servletRequest.getServletRequest().getSession(false);
   if (session != null) {
    attributes.put("HTTPSESSIONID", session.getId());
   }
  }
  return true;
 }
 
 public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
 }
}

И чтобы зарегистрировать этот HandshakeInterceptor с поддержкой Spring Websocket:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketDefaultConfig extends AbstractWebSocketMessageBrokerConfigurer {
 
 @Override
 public void configureMessageBroker(MessageBrokerRegistry config) {
  config.enableSimpleBroker("/topic/", "/queue/");
  config.setApplicationDestinationPrefixes("/app");
 }
 
 @Override
 public void registerStompEndpoints(StompEndpointRegistry registry) {
  registry.addEndpoint("/chat").withSockJS().setInterceptors(httpSessionIdHandshakeInterceptor());
 }
 
 @Bean
 public HttpSessionIdHandshakeInterceptor httpSessionIdHandshakeInterceptor() {
  return new HttpSessionIdHandshakeInterceptor();
 }
 
}

Теперь, когда идентификатор сеанса является частью заголовков STOMP, его можно получить как заголовок STOMP, ниже приведен пример, где он захватывается при регистрации подписок на сервере:

01
02
03
04
05
06
07
08
09
10
11
@Component
public class StompSubscribeEventListener implements ApplicationListener<SessionSubscribeEvent> {
 
 private static final Logger logger = LoggerFactory.getLogger(StompSubscribeEventListener.class);
 
 @Override
 public void onApplicationEvent(SessionSubscribeEvent sessionSubscribeEvent) {
  StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionSubscribeEvent.getMessage());
  logger.info(headerAccessor.getSessionAttributes().get("HTTPSESSIONID").toString());
 }
}

или его можно получить из метода контроллера, обрабатывающего сообщения websocket, в качестве параметра MessageHeaders :

1
2
3
4
5
@MessageMapping("/chats/{chatRoomId}")
 public void handleChat(@Payload ChatMessage message, @DestinationVariable("chatRoomId") String chatRoomId, MessageHeaders messageHeaders, Principal user) {
  logger.info(messageHeaders.toString());
  this.simpMessagingTemplate.convertAndSend("/topic/chats." + chatRoomId, "[" + getTimestamp() + "]:" + user.getName() + ":" + message.getMessage());
 }
  • Вот полный рабочий пример, который реализует этот шаблон.