WebSocket API

Модуль 5. Spring
Рівень 10 , Лекція 15
Відкрита

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.

Spring передбачає базовий клас 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>
Для конфігурації WebSocket на боці клієнта слід використовувати 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>
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ