websockets que example ejemplo cliente websocket java-ee-7

que - websocket tomcat java



Acceder a HttpSession desde HttpServletRequest en un socket Web @ ServerEndpoint (6)

¿Es posible obtener HttpServletRequest dentro de @ServerEndpoint? Principalmente estoy tratando de obtenerlo para poder acceder al objeto HttpSession.


Actualización (noviembre de 2016) : la información proporcionada en esta respuesta es para la especificación JSR356, las implementaciones individuales de la especificación pueden variar fuera de esta información. Otras sugerencias que se encuentran en los comentarios y otras respuestas son comportamientos específicos de implementación fuera de la especificación JSR356.

Si las sugerencias aquí le están causando problemas, actualice sus diversas instalaciones de Jetty, Tomcat, Wildfly o Glassfish / Tyrus. Se informó que todas las versiones actuales de esas implementaciones funcionan de la manera que se detalla a continuación.

Ahora volvamos a la respuesta original de agosto de 2013 ...

La respuesta de Martin Andersson tiene un error de concurrencia. El Configurador puede ser llamado por múltiples hilos al mismo tiempo, es probable que no tenga acceso al objeto HttpSession correcto entre las llamadas de modifyHandshake() y getEndpointInstance() .

O dicho de otra manera ...

  • Solicitud A
  • Modificar el apretón de manos A
  • Solicitud B
  • Modificar el apretón de manos B
  • Obtener Instancia de Endpoint A <- esto tendría HttpSession de Request B
  • Obtener la instancia de punto final B

Aquí hay una modificación del código de Martin que usa el mapa ServerEndpointConfig.getUserProperties() para hacer que HttpSession esté disponible para su instancia de socket durante la @OnOpen método @OnOpen

GetHttpSessionConfigurator.java

package examples; import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator { @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession)request.getHttpSession(); config.getUserProperties().put(HttpSession.class.getName(),httpSession); } }

GetHttpSessionSocket.java

package examples; import java.io.IOException; import javax.servlet.http.HttpSession; import javax.websocket.EndpointConfig; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/example", configurator = GetHttpSessionConfigurator.class) public class GetHttpSessionSocket { private Session wsSession; private HttpSession httpSession; @OnOpen public void open(Session session, EndpointConfig config) { this.wsSession = session; this.httpSession = (HttpSession) config.getUserProperties() .get(HttpSession.class.getName()); } @OnMessage public void echo(String msg) throws IOException { wsSession.getBasicRemote().sendText(msg); } }

Característica de bonificación: no se requiere instanceof o lanzamiento.

Algunos conocimientos de EndpointConfig

EndpointConfig objetos EndpointConfig existen por "Instancia de punto final".

Sin embargo, una "Instancia de punto final" tiene 2 significados con la especificación.

  1. Comportamiento predeterminado de JSR, donde cada solicitud de actualización entrante da como resultado una nueva instancia de objeto de la clase de punto final
  2. Una javax.websocket.Session que relaciona la instancia del punto final del objeto, con su configuración, con una conexión lógica específica.

Es posible tener una instancia de punto final javax.websocket.Session que se utilice para múltiples instancias de javax.websocket.Session (esta es una de las características que admite ServerEndpointConfig.Configurator )

La implementación de ServerContainer rastreará un conjunto de ServerEndpointConfig''s que representan todos los puntos finales implementados que el servidor puede responder a una solicitud de actualización de websocket.

Estas instancias de objetos ServerEndpointConfig pueden provenir de algunas fuentes diferentes.

  1. Proporcionado manualmente por javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
    • Por lo general, se realiza dentro de una llamada a javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce)
  2. Desde la javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set) .
  3. Creado automáticamente a partir del escaneo de la aplicación web para clases anotadas @ServerEndpoint .

Estas instancias de objeto ServerEndpointConfig existen como valores predeterminados para cuando finalmente se crea una javax.websocket.Session .

Instancia ServerEndpointConfig.Configurator

Antes de que se reciban o procesen las solicitudes de actualización, todos los objetos ServerEndpointConfig.Configurator ahora existen y están listos para realizar su principal y único propósito, para permitir la personalización del proceso de actualización de una conexión de websocket a eventualmente javax.websocket.Session

Acceso a la sesión específica EndpointConfig

Tenga en cuenta que no puede acceder a las instancias del objeto ServerEndpointConfig desde una instancia de punto final. Solo puede acceder a las instancias de EndpointConfig .

Esto significa que si proporcionó ServerContainer.addEndpoint(new MyCustomServerEndpointConfig()) durante la implementación y luego intentó acceder a él a través de las anotaciones, no funcionará.

Todo lo siguiente sería inválido.

@OnOpen public void onOpen(Session session, EndpointConfig config) { MyCustomServerEndpointConfig myconfig = (MyCustomServerEndpointConfig) config; /* this would fail as the config is cannot be cast around like that */ } // --- or --- @OnOpen public void onOpen(Session session, ServerEndpointConfig config) { /* For @OnOpen, the websocket implementation would assume that the ServerEndpointConfig to be a declared PathParam */ } // --- or --- @OnOpen public void onOpen(Session session, MyCustomServerEndpointConfig config) { /* Again, for @OnOpen, the websocket implementation would assume that the MyCustomServerEndpointConfig to be a declared PathParam */ }

Puede acceder a EndpointConfig durante la vida de la instancia del objeto Endpoint, pero en un tiempo limitado. Los javax.websocket.Endpoint.onOpen(Session,Endpoint) , anotado @OnOpen , o mediante el uso de CDI. EndpointConfig no está disponible de ninguna otra forma ni en ningún otro momento.

Sin embargo, siempre puede acceder a UserProperties a través de la llamada Session.getUserProperties() , que siempre está disponible. Este mapa de Propiedades del usuario siempre está disponible, ya sea a través de las técnicas comentadas (como un parámetro de sesión durante las @OnOpen , @OnClose , @OnError o @OnMessage ), a través de la inyección de CDI de la sesión, o incluso con el uso de -nuevos websockets que se extienden desde javax.websocket.Endpoint .

Cómo funciona la actualización

Como se indicó anteriormente, cada uno de los puntos finales definidos tendrá un ServerEndpointConfig asociado.

Esas ServerEndpointConfigs son una única instancia que representa el estado predeterminado de EndpointConfig que finalmente estará disponible para las instancias de punto final que posiblemente y finalmente se creen.

Cuando llega una solicitud de actualización entrante, se realiza lo siguiente en el JSR.

  1. la ruta coincide con cualquiera de las entradas ServerEndpointConfig.getPath ()
    • Si no hay coincidencia, devuelve 404 para actualizar
  2. pasar la solicitud de actualización a ServerEndpointConfig.Configurator.checkOrigin ()
    • Si no es válido, devuelve el error para actualizar la respuesta
    • crear HandshakeResponse
  3. pasar la solicitud de actualización a ServerEndpointConfig.Configurator.getNegotiatedSubprotocol ()
    • respuesta de la tienda en HandshakeResponse
  4. pasar la solicitud de actualización a ServerEndpointConfig.Configurator.getNegotiatedExtensions ()
    • respuesta de la tienda en HandshakeResponse
  5. Cree un nuevo objeto ServerEndpointConfig específico de punto final. copia codificadores, decodificadores y propiedades del usuario. Este nuevo ServerEndpointConfig ajusta por defecto la ruta, las extensiones, la clase de punto final, los subprotocolos, el configurador.
  6. pasar solicitud de actualización, respuesta y nuevo ServerEndpointConfig a ServerEndpointConfig.Configurator.modifyHandshake ()
  7. llamar a ServerEndpointConfig.getEndpointClass ()
  8. use clase en ServerEndpointConfig.Configurator.getEndpointInstance (Clase)
  9. crear sesión, asociar instancia de punto final y objeto EndpointConfig.
  10. Informar la instancia de punto final de conexión
  11. los métodos anotados que quieren EndpointConfig obtienen el asociado a esta Sesión.
  12. las llamadas a Session.getUserProperties () devuelven EndpointConfig.getUserProperties ()

Para tener en cuenta, el ServerEndpointConfig.Configurator es un singleton, por punto final de ServerContainer asignado.

Esto es intencional y deseado para permitir a los implementadores varias funciones.

  • para devolver la misma instancia de punto final para múltiples pares si así lo desean. El llamado enfoque sin estado para la escritura de websocket.
  • tener un único punto de administración de recursos costosos para todas las instancias de Endpoint

Si las implementaciones crearan un nuevo Configurador para cada apretón de manos, esta técnica no sería posible.

(Divulgación: escribo y mantengo la implementación JSR-356 para Jetty 9)


Prefacio

No está claro si quiere la HttpServletRequest , la HttpSession o las propiedades de HttpSession . Mi respuesta mostrará cómo obtener la HttpSession o las propiedades individuales.

He omitido las verificaciones de límites nulos e índices por brevedad.

Precauciones

Es complicado. La respuesta de Martin Andersson no es correcta porque se utiliza la misma instancia de ServerEndpointConfig.Configurator para cada conexión, por lo tanto, existe una condición de carrera. Si bien los documentos establecen que "La implementación crea una nueva instancia del configurador por punto final lógico", la especificación no define claramente un "punto final lógico". En función del contexto de todos los lugares en los que se usa esa frase, parece significar el enlace de una clase, configurador, ruta y otras opciones, es decir, un ServerEndpointConfig , que se comparte claramente. De todos modos, puede ver fácilmente si una implementación está utilizando la misma instancia imprimiendo su toString() desde modifyHandshake(...) .

Más sorprendentemente, la respuesta de Joakim Erdfelt tampoco funciona de manera confiable. El texto de JSR 356 no menciona EndpointConfig.getUserProperties() , solo está en JavaDoc, y en ninguna parte parece que se especifique cuál es su relación exacta con Session.getUserProperties() . En la práctica, algunas implementaciones (p. Ej., Glassfish) devuelven la misma instancia de Map para todas las llamadas a ServerEndpointConfig.getUserProperties() mientras que otras (p. Ej., Tomcat 8) no lo hacen. Puede verificar imprimiendo el contenido del mapa antes de modificarlo en modifyHandshake(...) .

Para verificar, copié el código directamente de las otras respuestas y luego lo probé contra un cliente multiproceso que escribí. En ambos casos observé que la sesión incorrecta estaba asociada con la instancia del punto final.

Esquema de soluciones

He desarrollado dos soluciones, que he verificado que funcionan correctamente cuando se prueban contra un cliente multiproceso. Hay dos trucos clave.

Primero, use un filtro con la misma ruta que WebSocket. Esto le dará acceso a HttpServletRequest y HttpSession . También te da la oportunidad de crear una sesión si aún no existe (aunque en ese caso parece dudoso usar una sesión HTTP).

En segundo lugar, encuentre algunas propiedades que existen tanto en la Session WebSocket como en HttpServletRequest o HttpSession . Resulta que hay dos candidatos: getUserPrincipal() y getRequestParameterMap() . Te mostraré cómo abusar de ambos :)

Solución utilizando el usuario principal

La forma más fácil es aprovechar Session.getUserPrincipal() y HttpServletRequest.getUserPrincipal() . La desventaja es que esto podría interferir con otros usos legítimos de esta propiedad, así que úsela solo si está preparado para esas implicaciones.

Si desea almacenar solo una cadena, como una identificación de usuario, esto no es demasiado abuso, aunque presumiblemente debe establecerse en alguna forma administrada por contenedor en lugar de anular la envoltura tal como se lo mostraré. De todos modos, harías eso solo anulando Principal.getName() . Entonces ni siquiera necesitas lanzarlo en el Endpoint . Pero si puede soportarlo, también puede pasar todo el objeto HttpSession siguiente manera.

PrincipalWithSession.java

package example1; import java.security.Principal; import javax.servlet.http.HttpSession; public class PrincipalWithSession implements Principal { private final HttpSession session; public PrincipalWithSession(HttpSession session) { this.session = session; } public HttpSession getSession() { return session; } @Override public String getName() { return ""; // whatever is appropriate for your app, e.g., user ID } }

WebSocketFilter.java

package example1; import java.io.IOException; import java.security.Principal; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @WebFilter("/example1") public class WebSocketFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; final PrincipalWithSession p = new PrincipalWithSession(httpRequest.getSession()); HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public Principal getUserPrincipal() { return p; } }; chain.doFilter(wrappedRequest, response); } public void init(FilterConfig config) throws ServletException { } public void destroy() { } }

WebSocketEndpoint.java

package example1; import javax.servlet.http.HttpSession; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/example1") public class WebSocketEndpoint { private HttpSession httpSession; @OnOpen public void onOpen(Session webSocketSession) { httpSession = ((PrincipalWithSession) webSocketSession.getUserPrincipal()).getSession(); } @OnMessage public String demo(String msg) { return msg + "; (example 1) session ID " + httpSession.getId(); } }

Solución mediante parámetros de solicitud

La segunda opción usa Session.getRequestParameterMap() y HttpServletRequest.getParameterMap() . Tenga en cuenta que utiliza ServerEndpointConfig.getUserProperties() pero es seguro en este caso porque siempre estamos colocando el mismo objeto en el mapa, por lo tanto, si se comparte no hace ninguna diferencia. El identificador de sesión único se pasa no a través de los parámetros de usuario, sino a través de los parámetros de solicitud, que es único por solicitud.

Esta solución es un poco menos hacky porque no interfiere con la propiedad principal del usuario. Tenga en cuenta que si necesita pasar los parámetros de solicitud reales además del que está insertado, puede hacerlo fácilmente: simplemente comience con el mapa de parámetros de solicitud existente en lugar de uno nuevo vacío como se muestra aquí. Pero tenga cuidado de que el usuario no pueda falsificar el parámetro especial agregado en el filtro suministrando su propio parámetro de solicitud con el mismo nombre en la solicitud HTTP real.

SessionTracker.java

/* A simple, typical, general-purpose servlet session tracker */ package example2; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; @WebListener public class SessionTracker implements ServletContextListener, HttpSessionListener { private final ConcurrentMap<String, HttpSession> sessions = new ConcurrentHashMap<>(); @Override public void contextInitialized(ServletContextEvent event) { event.getServletContext().setAttribute(getClass().getName(), this); } @Override public void contextDestroyed(ServletContextEvent event) { } @Override public void sessionCreated(HttpSessionEvent event) { sessions.put(event.getSession().getId(), event.getSession()); } @Override public void sessionDestroyed(HttpSessionEvent event) { sessions.remove(event.getSession().getId()); } public HttpSession getSessionById(String id) { return sessions.get(id); } }

WebSocketFilter.java

package example2; import java.io.IOException; import java.util.Collections; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @WebFilter("/example2") public class WebSocketFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; final Map<String, String[]> fakedParams = Collections.singletonMap("sessionId", new String[] { httpRequest.getSession().getId() }); HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public Map<String, String[]> getParameterMap() { return fakedParams; } }; chain.doFilter(wrappedRequest, response); } @Override public void init(FilterConfig config) throws ServletException { } @Override public void destroy() { } }

WebSocketEndpoint.java

package example2; import javax.servlet.http.HttpSession; import javax.websocket.EndpointConfig; import javax.websocket.HandshakeResponse; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; @ServerEndpoint(value = "/example2", configurator = WebSocketEndpoint.Configurator.class) public class WebSocketEndpoint { private HttpSession httpSession; @OnOpen public void onOpen(Session webSocketSession, EndpointConfig config) { String sessionId = webSocketSession.getRequestParameterMap().get("sessionId").get(0); SessionTracker tracker = (SessionTracker) config.getUserProperties().get(SessionTracker.class.getName()); httpSession = tracker.getSessionById(sessionId); } @OnMessage public String demo(String msg) { return msg + "; (example 2) session ID " + httpSession.getId(); } public static class Configurator extends ServerEndpointConfig.Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { Object tracker = ((HttpSession) request.getHttpSession()).getServletContext().getAttribute( SessionTracker.class.getName()); // This is safe to do because it''s the same instance of SessionTracker all the time sec.getUserProperties().put(SessionTracker.class.getName(), tracker); super.modifyHandshake(sec, request, response); } } }

Solución para propiedades individuales

Si solo necesita ciertas propiedades de la HttpSession y no toda la HttpSession sí misma, como por ejemplo una ID de usuario, entonces podría deshacerse de todo el negocio de SessionTracker y simplemente colocar los parámetros necesarios en el mapa que devuelve de su anulación de HttpServletRequestWrapper.getParameterMap() . Entonces también puede deshacerse del Configurator personalizado; sus propiedades serán convenientemente accesibles desde Session.getRequestParameterMap() en el Endpoint.

WebSocketFilter.java

package example5; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @WebFilter("/example5") public class WebSocketFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; final Map<String, String[]> props = new HashMap<>(); // Add properties of interest from session; session ID // is just for example props.put("sessionId", new String[] { httpRequest.getSession().getId() }); HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public Map<String, String[]> getParameterMap() { return props; } }; chain.doFilter(wrappedRequest, response); } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }

WebSocketEndpoint.java

package example5; import java.util.List; import java.util.Map; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/example5") public class WebSocketEndpoint { private Map<String, List<String>> params; @OnOpen public void onOpen(Session session) { params = session.getRequestParameterMap(); } @OnMessage public String demo(String msg) { return msg + "; (example 5) session ID " + params.get("sessionId").get(0); } }


¿Es posible?

Repasemos la API de Java para la especificación WebSocket para ver si es posible HttpSession objeto HttpSession . La specification dice en la página 29:

Debido a que las conexiones de websocket se inician con una solicitud http, existe una asociación entre la HttpSession bajo la cual opera un cliente y cualquier websocket que esté establecido dentro de esa HttpSession. La API permite el acceso en el handshake de apertura a la HttpSession única correspondiente a ese mismo cliente.

Entonces sí, es posible.

Sin embargo, no creo que sea posible obtener una referencia al objeto HttpServletRequest . Podrías escuchar todas las solicitudes de servlets nuevas usando ServletRequestListener , pero aún tendrías que averiguar qué solicitud pertenece a qué extremo del servidor. ¡Por favor avísame si encuentras una solución!

Resumen de cómo hacerlo

Cómo se describe holgadamente en las páginas 13 y 14 en la especificación y ejemplificado por mí en el código bajo el título siguiente.

En inglés, tendremos que interceptar el proceso de handshake para obtener un objeto HttpSession . Para transferir la referencia HttpSession a nuestro punto final del servidor, también debemos interceptar cuando el contenedor crea la instancia del punto final del servidor e inyectar manualmente la referencia. Hacemos todo esto al proporcionar nuestro propio ServerEndpointConfig.Configurator y ServerEndpointConfig.Configurator los métodos modifyHandshake() y getEndpointInstance() .

El configurador personalizado se creará una instancia una vez por punto de ServerEndpoint lógico (Ver JavaDoc ).

Ejemplo de código

Esta es la clase de punto final del servidor (proporciono la implementación de la clase CustomConfigurator después de este fragmento de código):

@ServerEndpoint(value = "/myserverendpoint", configurator = CustomConfigurator.class) public class MyServerEndpoint { private HttpSession httpSession; public void setHttpSession(HttpSession httpSession) { if (this.httpSession != null) { throw new IllegalStateException("HttpSession has already been set!"); } this.httpSession = httpSession; } @OnOpen public void onOpen(Session session, EndpointConfig config) { System.out.println("My Session Id: " + httpSession.getId()); } }

Y este es el configurador personalizado:

public class CustomConfigurator extends ServerEndpointConfig.Configurator { private HttpSession httpSession; // modifyHandshake() is called before getEndpointInstance()! @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { httpSession = (HttpSession) request.getHttpSession(); super.modifyHandshake(sec, request, response); } @Override public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException { T endpoint = super.getEndpointInstance(endpointClass); if (endpoint instanceof MyServerEndpoint) { // The injection point: ((MyServerEndpoint) endpoint).setHttpSession(httpSession); } else { throw new InstantiationException( MessageFormat.format("Expected instanceof /"{0}/". Got instanceof /"{1}/".", MyServerEndpoint.class, endpoint.getClass())); } return endpoint; } }



Todas las respuestas anteriores valen la pena leerlas, pero ninguna de ellas resuelve el problema de OP (y mi).

Puede acceder a HttpSession cuando se abre un punto final de WS y pasarlo a la instancia de punto final recién creada, pero nadie garantiza que exista una instancia de HttpSession.

Entonces, necesitamos el paso 0 antes de esta piratería (odio la implementación de JSR 365 de WebSocket). Websocket - httpSession devuelve nulo


Todas las soluciones posibles se basan en:

A. Las implementaciones del navegador cliente mantienen la ID de la sesión a través del valor de Cookie pasado como encabezado HTTP, o (si las cookies están deshabilitadas) es administrado por el contenedor Servlet que generará el postfix de ID de sesión para las URL generadas

B. Puede acceder a encabezados de solicitud HTTP solo durante el protocolo de enlace HTTP; después de eso, es el protocolo Websocket

Así que eso...

Solución 1: use "handshake" para acceder a HTTP

Solución 2: En su JavaScript del lado del cliente, genere dinámicamente el parámetro de ID de sesión HTTP y envíe el primer mensaje (a través de Websocket) que contenga esta ID de sesión. Conecte el "punto final" a la clase de caché / utilidad manteniendo la ID de sesión -> mapeo de sesión; Evite las pérdidas de memoria, puede usar el Listener de sesión, por ejemplo, para eliminar la sesión de la memoria caché.

PD: Agradezco las respuestas de Martin Andersson y Joakim Erdfelt. Desafortunadamente, la solución de Martin no es segura para subprocesos ...