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).
Було створення запит на усунення попереднього обмеження WebSocket API на Java, який можна знайти за адресою. Tomcat, Undertow та WebSphere передбачають власні альтернативні API, що дозволяють зробити це, а також це можливо здійснити за допомогою Jetty. Сподіваємося, що те саме буде зроблено і для інших серверів.
Другорядним моментом є те, що контейнери сервлетів за допомогою 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>
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ