Доброго времени суток уважаемые хабравчане. На моем текущем месте работы было принято решение перевести взаимодействие с web клиентом на WebSocket. Серверная часть написана на Java с использованием фреймворка Spring. В данной статье я хотел поделиться особенностью устройства Spring WebSocket.

WebSocket обеспечивает двустороннюю связь между клиентом и сервером, используя одно TCP соединение.

Протокол состоит из двух фаз:


Для Handshake запроса используется HTTP GET запрос, в результате которого происходит обновление соединения до WebSocket.

В данной статье мы подробно разберем механизм установления связи между клиентом и сервером, прокачкой соединения к WebSocket и пересылкой сообщения в Spring приложении.
В разборе будем использовать Spring-WebSocket и Annotation based конфигурацию.

Создание конфигурационного класса


Итак, для начала нам нужно объявить точку доступа, к которой будет обращаться клиент для создания соединения и отправки данных.

Для использования WebSocket в конфигурационном классе нам необходимо использовать аннотацию @EnableWebSocket. Из описания данной аннотации следует необходимость реализовать интерфейс WebSocketConfigurer нашим конфигурационным классом. Интерфейс WebSocketConfigurer содержит единственный метод registerWebSocketHandlers(WebSocketHandlerRegistry registry). Используя входной параметр WebSocketHandlerRegistry мы осуществляем добавление обработчиков (WebSocketHandler) входящих сообщений на определенный url.

Рассмотрим работу аннотации @EnableWebSocket более подробно.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebSocketConfiguration.class)
public @interface EnableWebSocket {
}

Основная задача данной аннотации — импорт конфигурационного класса DelegatingWebSocketConfiguration, который при помощи @Autowired получит экземпляры интерфейса WebSocketConfigurer (наш конфигурационный класс). Данные экземпляры WebSocketConfigurer используются в родительском классе WebSocketConfigurationSupport для создания бина HandlerMapping.

Создание бина HandlerMapping необходимо чтобы в дальнейшем DispatcherServlet смог определить обработчик для данного url.


Для преобразования WebSocketHandler в экземпляр HandlerMapping нам потребуются адаптеры WebSocketHttpRequestHandler и WebSocketHandlerMapping.

При помощи WebSocketHttpRequestHandler мы произведем приведение WebSocketHandler к HttpRequestHandler. А далее используя связку url, на который мы ждем запроса по открытию WebSocket, и HttpRequestHandler создадим экземпляр WebSocketHandlerMapping, который и будет зарегистрирован в DispatcherServlet для обработки HTTP запроса.


Когда клиент посылает запрос на открытие WebSocket соединения, запрос через DispatcherServlet приходит на метод handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) нашего декоратора WebSocketHttpRequestHandler.

В данном методе происходит вызов Interceptors, объявленных в конфигурационном классе при задании WebSocketHandlers, и вызов метода doHandshake, дефолтного или пользовательского экземпляра HandshakeHandler.


Обработка Handshake запроса


На работе метода doHandshake остановимся чуть подробнее. Здесь и происходит вся магия преобразования соединения к WebSockets. Но для начала давайте разберемся с параметрами пользовательского запроса и серверного ответа.

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

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
 Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

  • Origin — содержит url, с которого производится запрос. Используется для верификации допустимых адресов.
  • Sec-WebSocket-Protocol — определяет набор под-протоколов, к примеру, STOMP, который будет рассмотрен в следующих статьях.
  • Sec-WebSocket-Key — случайный ключ, который генерируется браузером: 16 байт в кодировке Base64.
  • Sec-WebSocket-Version — версия протокола.
  • Sec-WebSocket-Extensions — дополнительные расширения, например, permessage-deflate говорит о том, что сообщения будут передавать в сжатом виде.

Типичный ответ от сервера:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Код ответа HTTP 101 говорит об переключении протокола, в нашем случае на WebSocket.
Sec-WebSocket-Accept — рассчитанное значение на основе переданного Sec-WebSocket-Key и константы «258EAFA5-E914-47DA-95CA-C5AB0DC85B11» — по сути это подтверждение от сервера о готовности инициировать WebSocket соединение.

Работу метода doHandshake можно схематично представить в таком виде:


Стратегия обновления запроса до WebSocket


На данный момент доступны стратегии обновления соединения

  • TomcatRequestUpgradeStrategy
  • JettyRequestUpgradeStrategy
  • UndertowRequestUpgradeStrategy
  • GlassFishRequestUpgradeStrategy
  • WebLogicRequestUpgradeStrategy
  • WebSphereRequestUpgradeStrategy

Процесс обновления стратегии состоит из следующих шагов:


* Экземпляр EndPoint создается путем оборачивания WebSocketHandler в StandardWebSocketHandlerAdapter, который и является наследником EndPoint. Для создания сессии используется StandardWebSocketSession.

** Для обновления HttpServletRequest используется стандартный метод данного интерфейса update, в который передаем реализацию HttpUpgradeHandler,
в случае работы с Tomcat это WsHttpUpgradeHandler. При инициализации экземпляра HttpUpgradeHandler происходит создание и регистрация EndPonit в WebSocketContainer.

После данных настроек наша реализация WebSocketHandler готова принимать входящие сообщения, а используя WebSocketSession мы получили возможность отправлять сообщения клиенту.

Спасибо большое за внимание. В следующих статьях рассмотрим работу fallback механизма при помощи SockJS и возможности под-протокола STOMP.

Использованные источники:

> The WebSocket Protocol
> Spring WebSocket

Комментарии (2)


  1. BigDflz
    20.11.2017 23:00

    И пожалуйста удили внимание для создания WSS подключения, в том числе и для создания подключения с самоподписанным сертификатом.


    1. PavelMel Автор
      20.11.2017 23:00

      Здравствуйте, согласен, тема обеспечения безопасности при WebSocket соединении и шифрование трафика, заслуживает отдельной статьи.