servlets - Accediendo a ServletContext y HttpSession en @OnMessage de un JSR-356 @ServerEndpoint
java-ee websocket (3)
Código actualizado para la respuesta de BalusC, el método onOpen debe estar decorado con @OnOpen. Entonces ya no hay necesidad de extender la clase Endpoint:
@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {
private EndpointConfig config;
@OnOpen
public void onOpen(Session websocketSession, EndpointConfig config) {
this.config = config;
}
@OnMessage
public void onMessage(String message) {
HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
ServletContext servletContext = httpSession.getServletContext();
// ...
}
}
Necesito obtener el ServletContext
desde un @ServerEndpoint
para encontrar Spring ApplicationContext
y buscar un Bean.
Por el momento, mi mejor enfoque es vincular ese bean en el contexto de nombres JNDI y buscarlo en el Endpoint
. Cualquier mejor solución es bienvenida.
También estoy buscando una forma razonable de sincronizar HttpSession
con la Session
de websocket.
El servlet HttpSession
está en JSR-356 disponible por HandshakeRequest#getHttpSession()
que a su vez está disponible cuando se realiza una solicitud de handshake justo antes de @OnOpen
de @ServerEndpoint
. El ServletContext
está a su vez solo disponible a través de HttpSession#getServletContext()
. Son dos pájaros de un tiro.
Para capturar la solicitud de protocolo de enlace, implemente un ServerEndpointConfig.Configurator
y anule el método modifyHandshake()
. El HandshakeRequest
está disponible aquí como argumento de método. Puede poner HttpSession
en EndpointConfig#getUserProperties()
. El EndpointConfig
está a su vez disponible como argumento de método @OnOpen
.
Aquí hay un ejemplo de inicio de la implementación de ServerEndpointConfig.Configurator
:
public class ServletAwareConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put("httpSession", httpSession);
}
}
A continuación, le mostramos cómo puede usarlo, tenga en cuenta el atributo de configurator
del @ServerEndpoint
:
@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {
private EndpointConfig config;
@OnOpen
public void onOpen(Session websocketSession, EndpointConfig config) {
this.config = config;
}
@OnMessage
public void onMessage(String message) {
HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
ServletContext servletContext = httpSession.getServletContext();
// ...
}
}
Como sugerencia de diseño, es mejor mantener su @ServerEndpoint
completamente libre de dependencias API servlet. En la implementación modifyHandshake()
mejor extraer inmediatamente la información (generalmente Javabean mutable) que necesita de la sesión o contexto de servlet y colocarla en el mapa de propiedades del usuario. Si no lo hace, debe tener en cuenta que una sesión de websocket puede vivir más tiempo que la sesión HTTP. Entonces, cuando todavía llevas HttpSession
al punto final, puedes encontrarte con IllegalStateException
cuando tratas de acceder mientras está expirando.
En caso de que tenga CDI (y quizás JSF) en las manos, puede obtener inspiración del código fuente de OmniFaces <o:socket>
(los enlaces están en la parte inferior de la vitrina).
Ver también:
Probé la respuesta de BalusC en Tomcat (Versiones 7.0.56 y 8.0.14). En ambos contenedores, el parámetro de solicitud modifyHandshake no contiene una HttpSession (y por lo tanto no servletContext). Como necesitaba el contexto del servlet solo para acceder a las variables "globales" (es decir, la aplicación web global), acabo de almacenar estas variables en un campo estático ordinario de una clase de titular. Esto es poco elegante, pero funcionó.
Eso se asemeja a un error en esta versión específica de Tomcat. ¿Alguien por ahí también ha visto esto?