Spring Framework предоставляет API для протокола WebSocket, который можно использовать для написания клиентских и серверных приложений, обрабатывающих сообщения WebSocket.
WebSocketHandler
Создать сервер WebSocket так же просто, как реализовать WebSocketHandler
или, что более вероятно, расширить TextWebSocketHandler
или BinaryWebSocketHandler
. В следующем примере используется TextWebSocketHandler
:
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
Существует специальная Java-конфигурация WebSocket и поддержка пространства имен XML для отображения предыдущего обработчика WebSocket на определенный URL-адрес, как показано в следующем примере:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
В следующем примере показан XML-эквивалент конфигурации из предыдущего примера:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
Предыдущий пример предназначен для использования в приложениях Spring MVC и должен быть включен в конфигурацию DispatcherServlet
. Однако поддержка WebSocket в Spring не зависит от Spring MVC. С помощью WebSocketHandler
относительно просто интегрировать WebSocketHandler
в другие среды, работающие с HTTP, с помощью WebSocketHttpRequestHandler
.
При использовании API WebSocketHandler
напрямую или косвенно, например, через обмен сообщениями STOMP, приложение должно синхронизировать отправку сообщений, поскольку лежащая в основе стандартная сессия WebSocket (JSR-356) не допускает одновременной отправки. Один из вариантов – обернуть WebSocketSession
с помощью ConcurrentWebSocketSessionDecorator
.
Подтверждения установления связи по протоколу WebSocket
Самый простой способ настроить начальный HTTP-запрос подтверждения установления связи для WebSocket – это HandshakeInterceptor
, который открывает методы "перед" и "после" подтверждения установления связи. Можно использовать такой перехватчик, чтобы исключить подтверждение установления связи или открыть доступ к каким-либо атрибутам для WebSocketSession
. В следующем примере используется встроенный перехватчик для передачи атрибутов HTTP-сессии в WebSocket-сессию:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/myHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
В следующем примере показан XML-эквивалент конфигурации из предыдущего примера:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:handshake-interceptors>
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
Более продвинутым вариантом является расширение DefaultHandshakeHandler
, который выполняет все шаги подтверждения установления связи для WebSocket, включая проверку происхождения клиента, согласование субпротокола и другое. Приложению также может понадобиться использовать эту опцию, если ему необходимо сконфигурировать кастомнуюRequestUpgradeStrategy
для адаптации к движку и версии сервера WebSocket, которые еще не поддерживаются. Как Java-конфигурация, так и пространство имен XML позволяют сконфигурировать кастомный HandshakeHandler
.
WebSocketHandlerDecorator
, который можно использовать для декорирования WebSocketHandler
дополнительной логикой работы. Реализации журналирования и обработки исключений предусмотрены и добавляются по умолчанию при использовании Java-конфигурации WebSocket или пространства имен XML. ExceptionWebSocketHandlerDecorator
перехватывает все неперехваченные исключения, возникающие из любого метода WebSocketHandler
, и закрывает сессию WebSocket со статусом 1011
, который указывает на ошибку сервера.Развертывание
Spring WebSocket API легко интегрировать в приложение на Spring MVC, если DispatcherServlet
обрабатывает как подтверждение установления связи по протоколу WebSocket через HTTP, так и другие HTTP запросы. Его также легко интегрировать в другие сценарии HTTP-обработки путём вызова WebSocketHttpRequestHandler
. Это удобно и понятно. Однако в отношении сред выполнения JSR-356 действуют особые соображения.
WebSocket API на Java (JSR-356) предусматривает два механизма развертывания. Первый включает сканирование classpath контейнера сервлетов (функция Servlet 3) при запуске. Другой – это API регистрации для использования при инициализации контейнера сервлетов. Ни один из этих механизмов не позволяет использовать единый "фронтальный контроллер" для всех процедур HTTP-обработки – включая подтверждение установления связи по протоколу WebSocket и все остальные HTTP-запросы – такой как, например, DispatcherServlet
в Spring MVC.
Это существенное ограничение JSR-356, которое средства поддержки WebSocket в Spring решают с помощью специфических для сервера реализаций RequestUpgradeStrategy
даже при работе в среде выполнения JSR-356. В настоящее время такие стратегии существуют для Tomcat, Jetty, GlassFish, WebLogic, WebSphere и Undertow (и WildFly).
Второстепенным моментом является то, что контейнеры сервлетов с поддержкой JSR-356 должны выполнять сканирование ServletContainerInitializer
(SCI), которое может замедлить запуск приложения – в некоторых случаях значительно. Если после обновления до версии контейнера сервлетов с поддержкой JSR-356 наблюдается значительное замедление, должна быть возможность выборочно активировать или дезактивировать веб-фрагменты (и сканирование SCI) с помощью элемента <absolute-ordering />
в web.xml
, как показано в следующем примере:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering/>
</web-app>
Затем можно выборочно активировать веб-фрагменты по имени, например, собственный SpringServletContainerInitializer
из Spring, который обеспечивает поддержку API инициализации на Java.для Servlet 3 В следующем примере показано, как это сделать:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering>
<name>spring_web</name>
</absolute-ordering>
</web-app>
Серверная конфигурация
Каждый базовый механизм протокола WebSocket открывает свойства конфигурации, которые управляют характеристиками времени выполнения, такими как размер буфера сообщений, время ожидания бездействия и другие.
Для Tomcat, WildFly и GlassFish можно добавить ServletServerContainerFactoryBean
в Java-конфиг WebSocket, как показано в следующем примере:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
}
В следующем примере показан XML-эквивалент конфигурации из предыдущего примера:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<bean class="org.springframework...ServletServerContainerFactoryBean">
<property name="maxTextMessageBufferSize" value="8192"/>
<property name="maxBinaryMessageBufferSize" value="8192"/>
</bean>
</beans>
WebSocketContainerFactoryBean
(XML) или ContainerProvider.getWebSocketContainer()
(Java-конфигурация).В случае Jetty необходимо предоставить предварительно настроенную WebSocketServerFactory
для Jetty и подключить ее к DefaultHandshakeHandler
из Spring через Java-конфиг для WebSocket. В следующем примере показано, как это сделать:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(),
"/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
}
В следующем примере показан XML-эквивалент конфигурации из предыдущего примера:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/echo" handler="echoHandler"/>
<websocket:handshake-handler ref="handshakeHandler"/>
</websocket:handlers>
<bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
<constructor-arg ref="upgradeStrategy"/>
</bean>
<bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
<constructor-arg ref="serverFactory"/>
</bean>
<bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
<constructor-arg>
<bean class="org.eclipse.jetty...WebSocketPolicy">
<constructor-arg value="SERVER"/>
<property name="inputBufferSize" value="8092"/>
<property name="idleTimeout" value="600000"/>
</bean>
</constructor-arg>
</bean>
</beans>
Допустимые источники
Начиная с версии Spring Framework 4.1.5, логика работы по умолчанию для WebSocket и SockJS состоит в том, чтобы принимать запросы только из одного и того же источника. Также можно разрешить все или определенный список источников. Данная проверка в основном предназначена для клиентов браузеров. Ничто не мешает другим типам клиентов изменять значение заголовка Origin
(подробную информацию см.в разделе "RFC 6454: Концепция Web Origin").
Возможны следующие три варианта логики работы:
-
Разрешить только запросы из одного и того же источника (по умолчанию): В этом режиме, если SockJS активирован, встроенный фрейм (Iframe) HTTP-ответа для заголовка
X-Frame-Options
устанавливается вSAMEORIGIN
, а механизм передачи JSONP отключен, поскольку он не позволяет проверить источник запроса. Как следствие, отсутствует поддержка IE6 и IE7, если этот режим активирован. -
Разрешить заданный список источников: Каждый допустимый источник должен начинаться с
http://
илиhttps://
. В этом режиме, если SockJS активирован, передача iframe отключена. Как следствие, отсутствует поддержка IE6 через IE9, если этот режим активирован. -
Разрешить любые источники: Чтобы активировать этот режим, нужно указать
*
в качестве допустимого значения источника. В этом режиме доступны все механизмы передачи.
Вы можете настроить допустимые источники WebSocket и SockJS, как показано в следующем примере:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
В следующем примере показан XML-эквивалент конфигурации из предыдущего примера:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers allowed-origins="https://mydomain.com">
<websocket:mapping path="/myHandler" handler="myHandler" />
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ