CodeGym /Cursos Java /Módulo 5. Spring /Respaldo a través del protocolo SockJS

Respaldo a través del protocolo SockJS

Módulo 5. Spring
Nivel 10 , Lección 16
Disponible

En la Internet pública, los servidores proxy restrictivos fuera de su control pueden impedir las comunicaciones WebSocket, ya sea porque no están configurados para enviar el encabezado Upgrade o porque cierran conexiones de larga duración, que parecen estar atenuado.

La solución a este problema es la emulación de WebSocket, es decir, intentar utilizar WebSocket primero y luego recurrir a tecnologías basadas en HTTP que emulan la comunicación WebSocket y proporcionan la misma API en el nivel de aplicación.

En la pila de servlets, Spring Framework proporciona soporte del lado del servidor (así como del lado del cliente) para el protocolo SockJS.

Breve descripción

El propósito de SockJS es permitir que las aplicaciones utilicen la API WebSocket, pero recurran a alternativas que no sean WebSocket en tiempo de ejecución si es necesario, sin tener que cambiar el código de la aplicación.

SockJS consta de:

SockJS está diseñado para su uso en navegadores. Utiliza varias técnicas para admitir una amplia gama de versiones de navegadores. Para obtener una lista completa de los navegadores y los tipos de motores de entrega SockJS, consulte la página cliente SockJS. Los mecanismos de transferencia se dividen en tres categorías generales: WebSocket, transmisión HTTP y sondeo largo HTTP. Para obtener una descripción general de estas categorías, consulte este artículo de blog.

El cliente SockJS comienza enviando GET/info para obtener información básica del servidor. Después de esto, debe decidir qué mecanismo de transferencia utilizar. Si es posible, se utiliza el protocolo WebSocket. De lo contrario, la mayoría de los navegadores tienen al menos una opción de transmisión HTTP. Si no hay ninguno, se utiliza el sondeo HTTP (largo).

Todas las solicitudes de transferencia tienen la siguiente estructura de URL:

https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

donde:

  • {server-id} se usa para enrutar solicitudes en el clúster, pero no se usa de otro modo.

  • {session-id} asigna solicitudes HTTP que pertenecen a una sesión de SockJS.

  • {transport} indica el tipo de mecanismo de transporte (por ejemplo, websocket, xhr-streaming y otros).

El mecanismo de transferencia WebSocket requiere solo una solicitud HTTP para confirmar el establecimiento del protocolo WebSocket. Todos los mensajes posteriores se envían a través de este socket.

Los mecanismos de transporte HTTP requieren más solicitudes. La transmisión Ajax/XHR, por ejemplo, utiliza una única solicitud de larga duración para pasar mensajes del servidor al cliente y solicitudes HTTP POST adicionales para pasar mensajes del cliente al servidor. El sondeo largo funciona de manera similar, excepto que finaliza la solicitud actual después de que cada dato se envía desde el servidor al cliente.

SockJS agrega un marco de mensaje simple. Por ejemplo, el servidor envía inicialmente la letra o (marco "abierto"), los mensajes se envían como a["message1", "message2"] (matriz codificada en JSON ), la letra h (marco "latido") si no se reciben mensajes dentro de 25 segundos (predeterminado) y la letra c (marco "cerrar") para cerrar el sesión.

Para obtener más información, ejecute el ejemplo en un navegador y vea las solicitudes HTTP. El cliente SockJS le permite capturar una lista de mecanismos de transferencia, para que pueda ver cada mecanismo por turno. El cliente SockJS también proporciona un indicador de depuración que le permite enviar mensajes útiles a la consola del navegador. En el lado del servidor, puede habilitar el registro TRACE para org.springframework.web.socket. Puede encontrar información aún más detallada en prueba del Protocolo SockJS con comentarios.

Activación de SockJS

Puedes activar SockJS a través de la configuración de Java, como se muestra en el siguiente ejemplo:


@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").withSockJS();
    }
    @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:sockjs/>
    </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 para WebSocket y SockJS es independiente de Spring MVC. Usando SockJsHttpRequestHandler es relativamente fácil de integrar en otros marcos HTTP.

En el lado del navegador, las aplicaciones pueden usar sockjs-client (versión 1.0.x). Emula la API WebSocket estandarizada por el consorcio W3C e interactúa con el servidor para seleccionar la mejor opción de transferencia según el navegador en el que se esté ejecutando. Consulte sockjs-client para obtener una lista de mecanismos de transferencia compatibles con el navegador. El cliente también proporciona varias opciones de configuración, por ejemplo, para especificar qué mecanismos de transporte incluir.

IE 8 y 9

Internet Explorer 8 y 9 todavía se utilizan. Son la razón clave para tener SockJS. Esta sección cubre aspectos importantes del trabajo en estos navegadores.

El cliente SockJS admite la transmisión Ajax/XHR en IE 8 y 9 usando XDomainRequest de Microsoft. Este método funciona en todos los dominios, pero no admite el envío de cookies. Las cookies suelen ser necesarias para que funcionen las aplicaciones Java. Sin embargo, dado que el cliente SockJS se puede utilizar con muchos tipos de servidores (no solo Java), es necesario comprender si las cookies tienen algún significado. En caso afirmativo, entonces el cliente SockJS preferirá Ajax/XHR para la transmisión. De lo contrario, se utiliza la técnica basada en iframe.

La primera solicitud /info del cliente SockJS es una solicitud de información que puede influir en la elección del mecanismo de transferencia por parte del cliente. Parte de esta información es si la aplicación del servidor utiliza cookies (por ejemplo, para autenticación o agrupación en sesiones fijas). El soporte de Spring para SockJS incluye una propiedad llamada sessionCookieNeeded. Esta característica está habilitada de forma predeterminada porque la mayoría de las aplicaciones Java utilizan la cookie JSESSIONID. Si su aplicación no lo requiere, puede desactivar esta opción y el cliente SockJS tendrá que seleccionar xdr-streaming en IE 8 y 9.

Si está utilizando un iframe basado en mecanismo de transmisión, tenga en cuenta que se puede indicar a los navegadores que bloqueen el uso de un IFrame en una página determinada configurando el encabezado de respuesta HTTP X-Frame-Options en DENY , SAMEORIGIN o PERMITIR DESDE <origen>. Esto se utiliza para evitar el clickjacking.

Spring Security 3.2+ proporciona soporte para configurar el encabezado X-Frame-Options para cada respuesta. De forma predeterminada, la configuración de Spring Security Java está establecida en DENY. En la versión 3.2, el espacio de nombres XML de Spring Security no establece este encabezado de forma predeterminada, pero se puede configurar para hacerlo. En el futuro, podrá establecer este título de forma predeterminada.

Ver: sección "Encabezados de seguridad predeterminados" Consulte Consulte la documentación de Spring Security para obtener detalles sobre cómo configurar las opciones del encabezado X-Frame-Options. También puede consultar gh-2718 para obtener más información.

Si su aplicación agrega un encabezado de respuesta X-Frame-Options (¡como debería!) y utiliza un mecanismo de entrega basado en iframe, entonces necesita configurar el valor del encabezado a SAMEORIGIN o ALLOW-FROM <origin>. El soporte de Spring para SockJS también necesita conocer la ubicación del cliente SockJS, ya que se carga desde un iframe. De forma predeterminada, el iframe está configurado para cargar el cliente SockJS desde la CDN. Es una buena idea configurar esta opción para usar una URL del mismo origen que la aplicación.

El siguiente ejemplo muestra cómo hacer esto usando la configuración de Java:


@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS()
                .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
    }
    // ...
}

El espacio de nombres XML proporciona una capacidad similar a través del elemento <websocket:sockjs>.

Durante el desarrollo previo, habilite el modo devel del cliente SockJS, lo que evitará que el navegador almacene en caché Las solicitudes de SockJS (como iframes) que, de otro modo, se almacenarían en caché. Para obtener más información sobre cómo activarlo, consulte la página del cliente SockJS.

Mensajes de latido

El protocolo SockJS requiere enviar mensajes de latido desde el lado del servidor para que los servidores proxy no puedan concluir que la conexión está bloqueada. La configuración de SockJS para Spring tiene una propiedad heartbeatTime que se puede usar para establecer la frecuencia. De forma predeterminada, se envía un latido después de 25 segundos, siempre que no se hayan enviado otros mensajes para la conexión. Este valor de 25 segundos es coherente con la siguiente recomendación IETF para aplicaciones públicas de Internet.

Cuando se utiliza STOMP sobre WebSocket y SockJS, si el cliente y el servidor STOMP acuerdan intercambiar mensajes de latido, los mensajes de latido de SockJS se desactivan.

SockJS Herramientas de soporte Spring también le permite configurar TaskScheduler para programar tareas de mensajes de latido. El programador de tareas utiliza un grupo de subprocesos cuya configuración predeterminada depende de la cantidad de procesadores disponibles. Debería considerar personalizar la configuración para satisfacer sus necesidades específicas.

Borrar la conexión del cliente

La transmisión HTTP y el sondeo HTTP largo requieren que los mecanismos de transferencia de SockJS mantengan la conexión abierta por más tiempo de lo habitual. Se proporciona una breve descripción de estos métodos en este artículo de blog.

En los contenedores de servlet, esto se logra mediante el soporte asincrónico de Servlet 3, que le permite salir del subproceso del contenedor de servlet, procesar la solicitud y continúe escribiendo la respuesta de otro hilo.

El problema específico es que la API de Servlet no proporciona notificaciones para un cliente que ha desaparecido. Consulte eclipse-ee4j/servlet-api#44. Sin embargo, los contenedores de servlets generan una excepción en intentos posteriores de escribir en la respuesta. Dado que el servicio Spring para SockJS admite mensajes de latido enviados por el servidor (cada 25 segundos de forma predeterminada), esto significa que generalmente se detecta una falla en la conexión del cliente dentro de este período de tiempo (o antes si los mensajes se envían con más frecuencia).

Como resultado, pueden ocurrir fallas de E/S de red debido a la pérdida de la conexión del cliente, lo que puede causar que el registro se llene con datos de seguimiento de pila innecesarios. Spring hace todo lo posible para identificar aquellas fallas de red que representan una pérdida de conexión con el cliente (específicas de cada servidor) y escribe un mensaje comprimido en el registro usando una categoría de registro especial, DISCONNECTED_CLIENT_LOG_CATEGORY (definida en AbstractSockJsSession). Si necesita ver el seguimiento de la pila, puede configurar la categoría de registro en TRACE.

SockJS y CORS

Si permite solicitudes entre diferentes orígenes, el protocolo SockJS utilizará CORS Mecanismo para soporte entre dominios en transmisión de transporte XHR y transporte de sondeo. Por lo tanto, los encabezados CORS se agregan automáticamente si no se detecta la presencia de encabezados CORS en la respuesta. Por lo tanto, si la aplicación ya está configurada para admitir CORS (por ejemplo, a través de un filtro de servlet), SockJsService de Spring omite esta parte.

También puede deshabilitar la adición de estos CORS encabezados configurando la propiedad suppressCors en SockJsService de Spring.

SockJS acepta los siguientes encabezados y valores:

  • Acceso-Control-Allow-Origin: Inicializado desde el valor del encabezado de solicitud Origin.

  • Access-Control-Allow-Credentials: siempre establecido en true.

  • Access-Control-Request-Headers: inicializado a partir de los valores del encabezado de solicitud equivalente.

  • Access-Control-Allow-Methods: métodos HTTP que admite el mecanismo de transporte (ver tipo de enumeración TransportType).

  • Access-Control-Max-Age: establece el valor en 31536000 (1 año).

Para conocer la implementación exacta, consulte addCorsHeaders en AbstractSockJsService y el tipo de enumeración TransportType en el código fuente.

Además, si la configuración CORS lo permite, considere excluir las URL con el prefijo SockJS, permitiendo así que SockJsService de Spring las maneje.

SockJsClient

Spring proporciona un cliente Java SockJS para conectarse a puntos finales SockJS remotos sin utilizar un navegador. Esto puede resultar especialmente útil si necesita proporcionar comunicación bidireccional entre dos servidores a través de una red pública (es decir, donde los servidores proxy de red pueden impedir el uso del protocolo WebSocket). El cliente Java SockJS también es muy útil para realizar pruebas (por ejemplo, para simular una gran cantidad de usuarios simultáneos).

El cliente Java SockJS admite websocket, xhr-streaming y xhr-polling. El resto sólo tiene sentido para su uso en un navegador.

Puedes configurar WebSocketTransport con:

  • StandardWebSocketClient en el tiempo de ejecución JSR-356.

  • JettyWebSocketClient, utilizando la API WebSocket integrada en Jetty 9+.

  • Cualquier implementación de WebSocketClient de Spring.

XhrTransport, por definición, admite ambos xhr-streaming y xhr-polling porque, desde el punto de vista del cliente, no existe otra diferencia que la URL utilizada para conectarse al servidor. Actualmente hay dos implementaciones:

  • RestTemplateXhrTransport usa RestTemplate de Spring para solicitudes HTTP.

  • JettyXhrTransport utiliza HttpClient de Jetty para solicitudes HTTP.

En el siguiente ejemplo se muestra cómo para crear un cliente SockJS y conectarse al punto final de SockJS:


List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
SockJS utiliza matrices en formato JSON para generar mensajes. El valor predeterminado es la biblioteca Jackson 2, que debería estar en el classpath. Además, puede configurar una implementación personalizada de SockJsMessageCodec y configurarla en SockJsClient.

Para usar SockJsClient para simular Para un gran número de usuarios simultáneos, es necesario configurar el cliente HTTP subyacente (para el mecanismo de transporte XHR) con un número suficiente de conexiones y subprocesos. El siguiente ejemplo muestra cómo hacer esto usando Maven:


HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

El siguiente ejemplo muestra las propiedades relacionadas con SockJS del lado del servidor (consulte javadoc para obtener más detalles) que debería también configure:


@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/sockjs").withSockJS()
            .setStreamBytesLimit(512 * 1024) 
            .setHttpMessageCacheSize(1000) 
            .setDisconnectDelay(30 * 1000); 
    }
    // ...
}
  1. Establezca la propiedad streamBytesLimit en 512 KB (predeterminado 128 KB - 128 * 1024).
  2. Establezca el de propiedad httpMessageCacheSize igual a 1000 (100 predeterminado).
  3. Establezca la propiedad disconnectDelay en 30 segundos (el valor predeterminado es cinco segundos - 5 * 1000).
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION