Primavera Framework proporciona una API para el protocolo WebSocket que puede utilizar para escribir aplicaciones de cliente y servidor que manejen mensajes WebSocket.

WebSocketHandler

Cree un servidor WebSocket el De la misma manera, simplemente cómo implementar WebSocketHandler o, más probablemente, extender TextWebSocketHandler o BinaryWebSocketHandler. El siguiente ejemplo utiliza 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) {
        // ...
    }
}

Hay un Java especial configuración Soporte de espacio de nombres XML y WebSocket para asignar un controlador WebSocket anterior a una URL específica, como se muestra en el siguiente ejemplo:


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();
    }
}

El siguiente ejemplo muestra el equivalente XML de la configuración del ejemplo anterior:


<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>

El ejemplo anterior está pensado para su uso en aplicaciones Spring MVC y debe incluirse en la configuración de DispatcherServlet. Sin embargo, el soporte de Spring WebSocket es independiente de Spring MVC. Con WebSocketHandler es relativamente fácil integrar WebSocketHandler en otros marcos HTTP usando WebSocketHttpRequestHandler.

Cuándo Al utilizar la API WebSocketHandler directa o indirectamente, como a través de la mensajería STOMP, la aplicación debe sincronizar el envío de mensajes porque la sesión WebSocket estándar subyacente (JSR-356) no permite el envío simultáneo. Una opción es empaquetar WebSocketSession con ConcurrentWebSocketSessionDecorator.

Apretón de enlace de WebSocket

La forma más sencilla de configurar el protocolo de enlace HTTP inicial La solicitud para WebSocket es un HandshakeInterceptor que expone los métodos de protocolo de enlace "antes" y "después". Puede utilizar dicho interceptor para evitar el protocolo de enlace o exponer algunos atributos a WebSocketSession. El siguiente ejemplo utiliza un interceptor integrado para pasar atributos de sesión HTTP a una sesión WebSocket:


@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

El siguiente ejemplo muestra el equivalente XML de la configuración del ejemplo anterior:


<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>

Una opción más avanzada es la extensión DefaultHandshakeHandler, que realiza todos los pasos del protocolo de enlace para un WebSocket, incluido comprobar origen de cliente, aprobación de subprotocolo, etc. Es posible que una aplicación también necesite usar esta opción si necesita configurar una RequestUpgradeStrategy personalizada para adaptarse a una versión del motor y del servidor WebSocket que aún no es compatible. Tanto la configuración de Java como el espacio de nombres XML le permiten configurar un HandshakeHandler personalizado.

Spring proporciona una clase base WebSocketHandlerDecorator, que se puede utilizar para decorar WebSocketHandler con lógica operativa adicional. Las implementaciones de registro y manejo de excepciones se proporcionan y agregan de forma predeterminada cuando se utiliza una configuración Java WebSocket o un espacio de nombres XML. ExceptionWebSocketHandlerDecorator detecta todas las excepciones no detectadas generadas por cualquier método WebSocketHandler y cierra la sesión de WebSocket con el estado 1011, lo que indica un error del servidor.

Implementación

La API Spring WebSocket es fácil de integrar en una aplicación Spring MVC si el DispatcherServlet maneja tanto el protocolo de enlace HTTP de WebSocket como otras solicitudes HTTP. También se puede integrar fácilmente en otros scripts de procesamiento HTTP llamando a WebSocketHttpRequestHandler. Es conveniente y comprensible. Sin embargo, existen consideraciones especiales para los tiempos de ejecución JSR-356.

La API Java WebSocket (JSR-356) proporciona dos mecanismos de implementación. El primero implica escanear el classpath del contenedor de servlet (función Servlet 3) al inicio. La otra es una API de registro que se utiliza al inicializar un contenedor de servlets. Ninguno de estos mecanismos permite que se utilice un único "controlador frontal" para todo el procesamiento HTTP, incluido el protocolo de enlace WebSocket y todas las demás solicitudes HTTP, como DispatcherServlet en Spring MVC.

Esto Una limitación importante de JSR-356 es que el soporte WebSocket de Spring aborda implementaciones RequestUpgradeStrategy específicas del servidor, incluso cuando se ejecuta en el tiempo de ejecución de JSR-356. Actualmente existen estrategias de este tipo para Tomcat, Jetty, GlassFish, WebLogic, WebSphere y Undertow (y WildFly).

Se ha creado una solicitud para resolver el problema anterior. limitación de WebSocket API en Java, que se puede encontrar en eclipse-ee4j/websocket-api# 211. Tomcat, Undertow y WebSphere proporcionan sus propias API alternativas para hacer esto, y también es posible hacerlo usando Jetty. Esperemos que se haga lo mismo con otros servidores.

Un punto secundario es que los contenedores de servlets habilitados para JSR-356 deben realizar un análisis ServletContainerInitializer (SCI), lo que puede ralentizar el inicio. de la solicitud, en algunos casos de forma significativa. Si experimenta una desaceleración significativa después de actualizar a una versión habilitada para JSR-356 del contenedor de servlets, debería poder habilitar o deshabilitar selectivamente los fragmentos web (y el escaneo SCI) usando Elemento <absolute-ordering/> en web.xml, como se muestra en el siguiente ejemplo:


<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>

Luego puede activar selectivamente fragmentos web por nombre, por ejemplo el propio SpringServletContainerInitializer de Spring, que proporciona soporte para la API de inicialización de Java para Servlet 3 El siguiente ejemplo muestra cómo hacer esto:


<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>

Configuración del servidor

Cada mecanismo del protocolo WebSocket subyacente expone propiedades de configuración que controlan las características del tiempo de ejecución, como tamaño del búfer de mensajes, tiempo de espera de inactividad y otros.

Para Tomcat, WildFly y GlassFish, puede agregar ServletServerContainerFactoryBean a la configuración de WebSocket Java, como se muestra en el siguiente ejemplo:


@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}

El siguiente ejemplo muestra el equivalente XML de la configuración del ejemplo anterior:


<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>
Para la configuración de WebSocket del lado del cliente, utilice WebSocketContainerFactoryBean (XML) o ContainerProvider.getWebSocketContainer() (configuración de Java).

En el caso de Jetty, debe proporcionar un preconfigurado WebSocketServerFactory para Jetty y conéctelo a DefaultHandshakeHandler desde Spring a través de la configuración de Java para WebSocket. El siguiente ejemplo muestra cómo hacer esto:

 
@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)));
    }
}

El siguiente ejemplo muestra el equivalente XML de la configuración del ejemplo anterior:


<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> 

Fuentes aceptables

A partir de Spring Framework 4.1.5, la lógica predeterminada para WebSocket y SockJS es solo aceptar solicitudes de la misma fuente. También puede permitir todas o una lista específica de fuentes. Esta verificación está destinada principalmente a clientes de navegador. No hay nada que impida que otros tipos de clientes cambien el valor del encabezado Origin (consulte " para más detalles RFC 6454: Concepto de origen web).

Las siguientes tres opciones de lógica operativa son posibles:

  • Permitir solo solicitudes del mismo origen (predeterminado): en este modo, si SockJS está habilitado, el marco en línea de respuesta HTTP (Iframe) para el encabezado X-Frame-Options se establece en SAMEORIGIN y el mecanismo de paso JSONP está deshabilitado porque no le permite verificar el origen de la solicitud. Como resultado, no hay soporte para IE6 e IE7 si este modo está habilitado.

  • Permitir una lista especificada de fuentes: cada fuente válida debe comenzar con http:// o https://. En este modo, si SockJS está activado, el paso de iframe está deshabilitado. Como resultado, no hay soporte para IE6 a IE9 si este modo está habilitado.

  • Permitir cualquier fuente: para habilitar este modo, debe especificar * como valores fuente válidos. En este modo, todos los mecanismos de transmisión están disponibles.

Puedes configurar fuentes WebSocket y SockJS válidas, como se muestra en el siguiente ejemplo:


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();
    }
}

El siguiente ejemplo muestra el equivalente XML de la configuración del ejemplo anterior:


<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>