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:
protocolo SockJS definido como ejecutables pruebas con comentarios.
Cliente SockJS de JavaScript: una biblioteca cliente para usar en navegadores.
Implementaciones del servidor SockJS, incluida una en el módulo
spring-websocket
de Spring Framework.Cliente Java SockJS en módulo
spring-websocket
(desde la versión 4.1).
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>
.
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.
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).
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 solicitudOrigin
.Access-Control-Allow-Credentials
: siempre establecido entrue
.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ónTransportType
).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
usaRestTemplate
de Spring para solicitudes HTTP.JettyXhrTransport
utilizaHttpClient
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");
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);
}
// ...
}
- Establezca la propiedad
streamBytesLimit
en 512 KB (predeterminado 128 KB -128 * 1024
). - Establezca el de propiedad
httpMessageCacheSize
igual a 1000 (100
predeterminado). - Establezca la propiedad
disconnectDelay
en 30 segundos (el valor predeterminado es cinco segundos -5 * 1000
).
GO TO FULL VERSION