web-dev-qa-db-fra.com

Accès à HttpSession à partir de HttpServletRequest dans un socket Web @ServerEndpoint

Est-il possible d'obtenir le HttpServletRequest dans un @ServerEndpoint? J'essaie principalement de l'obtenir afin de pouvoir accéder à l'objet HttpSession.

45
Archimedes Trajano

Mise à jour (novembre 2016): Les informations fournies dans cette réponse concernent la spécification JSR356, les implémentations individuelles de la spécification peuvent varier en dehors de ces informations. Les autres suggestions trouvées dans les commentaires et autres réponses sont toutes des comportements spécifiques à l'implémentation en dehors de la spécification JSR356.

Si les suggestions ici vous posent des problèmes, mettez à niveau vos différentes installations de Jetty, Tomcat, Wildfly ou Glassfish/Tyrus. Il a été signalé que toutes les versions actuelles de ces implémentations fonctionnent de la manière décrite ci-dessous.

Revenons maintenant à la réponse originale de août 201 ...

La réponse de Martin Andersson a un défaut de concurrence. Le configurateur peut être appelé par plusieurs threads en même temps, il est probable que vous n'aurez pas accès à l'objet HttpSession correct entre les appels de modifyHandshake() et getEndpointInstance().

Ou dit une autre façon ...

  • Demande A
  • Modifier la poignée de main A
  • Demande B
  • Modifier la poignée de main B
  • Get Endpoint Instance A <- cela aurait HttpSession de la demande B
  • Obtenir l'instance de point de terminaison B

Voici une modification du code de Martin qui utilise ServerEndpointConfig.getUserProperties() map pour rendre le HttpSession disponible pour votre instance de socket lors de l'appel de la méthode _@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);
    }
}
_

Bonus: pas de instanceof ou de casting requis.

Quelques connaissances EndpointConfig

EndpointConfig les objets existent par "Endpoint Instance".

Cependant, une "instance de point de terminaison" a 2 significations avec la spécification.

  1. Comportement par défaut du JSR, où chaque demande de mise à niveau entrante entraîne une nouvelle instance d'objet de la classe de point de terminaison
  2. Un _javax.websocket.Session_ qui relie l'instance de point de terminaison d'objet, avec sa configuration, à une connexion logique spécifique.

Il est possible d'avoir une instance de point de terminaison singleton utilisée pour plusieurs instances de _javax.websocket.Session_ (c'est l'une des fonctionnalités prises en charge par _ServerEndpointConfig.Configurator_)

L'implémentation de ServerContainer suivra un ensemble de ServerEndpointConfig qui représentent tous les points de terminaison déployés auxquels le serveur peut répondre à une demande de mise à niveau Websocket.

Ces instances d'objet ServerEndpointConfig peuvent provenir de différentes sources.

  1. Fourni manuellement par la javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
    • Généralement effectué dans un appel javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce)
  2. A partir de l'appel javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set).
  3. Créé automatiquement à partir de l'analyse de l'application Web pour _@ServerEndpoint_ classes annotées.

Ces instances d'objet ServerEndpointConfig existent en tant que valeurs par défaut lorsqu'un _javax.websocket.Session_ est finalement créé.

Instance ServerEndpointConfig.Configurator

Avant la réception ou le traitement de toute demande de mise à niveau, tous les objets _ServerEndpointConfig.Configurator_ existent maintenant et sont prêts à remplir leur fonction principale et unique, pour permettre la personnalisation du processus de mise à niveau d'une connexion Websocket vers un éventuel _javax.websocket.Session_

Accès à EndpointConfig spécifique à la session

Remarque, vous ne pouvez pas accéder aux instances d'objet ServerEndpointConfig à partir d'une instance de noeud final. Vous ne pouvez accéder qu'aux instances EndpointConfig.

Cela signifie que si vous avez fourni ServerContainer.addEndpoint(new MyCustomServerEndpointConfig()) pendant le déploiement et que vous avez ensuite essayé d'y accéder via les annotations, cela ne fonctionnera pas.

Tous les éléments suivants ne seraient pas valides.

_@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
     */
}
_

Vous pouvez accéder à EndpointConfig pendant la durée de vie de l'instance d'objet Endpoint, mais sous un temps limité. Les méthodes javax.websocket.Endpoint.onOpen(Session,Endpoint), annotées _@OnOpen_, ou via l'utilisation de CDI. EndpointConfig n'est pas disponible d'une autre manière ni à aucun autre moment.

Cependant, vous pouvez toujours accéder aux UserProperties via l'appel Session.getUserProperties(), qui est toujours disponible. Cette carte des propriétés utilisateur est toujours disponible, que ce soit via les techniques annotées (comme un paramètre de session pendant _@OnOpen_, _@OnClose_, _@OnError_ ou _@OnMessage_ appels), via CDI injection de la Session, ou même avec l'utilisation de websockets non annotées qui s'étendent de _javax.websocket.Endpoint_.

Comment fonctionne la mise à nivea

Comme indiqué précédemment, chacun des points de terminaison définis sera associé à un ServerEndpointConfig.

Ces ServerEndpointConfigs sont une instance unique qui représente l'état par défaut des EndpointConfig qui sont éventuellement mis à la disposition des instances de point de terminaison qui sont éventuellement et éventuellement créées.

Lorsqu'une demande de mise à niveau entrante arrive, elle a suivi les étapes suivantes sur le JSR.

  1. le chemin correspond-il à l'une des entrées ServerEndpointConfig.getPath ()
    • Si aucune correspondance, retournez 404 pour mettre à niveau
  2. transmettre la demande de mise à niveau à ServerEndpointConfig.Configurator.checkOrigin ()
    • Si non valide, renvoie une erreur pour mettre à niveau la réponse
    • créer HandshakeResponse
  3. transmettre la demande de mise à niveau à ServerEndpointConfig.Configurator.getNegotiatedSubprotocol ()
    • stocker la réponse dans HandshakeResponse
  4. transmettre la demande de mise à niveau à ServerEndpointConfig.Configurator.getNegotiatedExtensions ()
    • stocker la réponse dans HandshakeResponse
  5. Créez un nouvel objet ServerEndpointConfig spécifique au noeud final. copier les encodeurs, décodeurs et propriétés utilisateur. Ce nouveau ServerEndpointConfig encapsule par défaut le chemin, les extensions, la classe de point de terminaison, les sous-protocoles, le configurateur.
  6. transmettre la demande de mise à niveau, la réponse et le nouveau ServerEndpointConfig dans ServerEndpointConfig.Configurator.modifyHandshake ()
  7. appeler ServerEndpointConfig.getEndpointClass ()
  8. utiliser la classe sur ServerEndpointConfig.Configurator.getEndpointInstance (Class)
  9. créer une session, associer une instance de point de terminaison et un objet EndpointConfig.
  10. Informer l'instance de point de terminaison de la connexion
  11. les méthodes annotées qui veulent que EndpointConfig obtient celle associée à cette session.
  12. les appels à Session.getUserProperties () renvoie EndpointConfig.getUserProperties ()

Pour noter, le ServerEndpointConfig.Configurator est un singleton, par point de terminaison ServerContainer mappé.

Ceci est intentionnel et souhaité pour permettre aux implémenteurs plusieurs fonctionnalités.

  • pour renvoyer la même instance de point de terminaison pour plusieurs homologues s'ils le souhaitent. L'approche dite sans état de l'écriture websocket.
  • disposer d'un point de gestion unique des ressources coûteuses pour toutes les instances de point final

Si les implémentations créaient un nouveau configurateur pour chaque prise de contact, cette technique ne serait pas possible.

(Divulgation: j'écris et maintiens l'implémentation JSR-356 pour Jetty 9)

72
Joakim Erdfelt

Préface

Il n'est pas clair si vous voulez le HttpServletRequest, le HttpSession ou les propriétés du HttpSession. Ma réponse montrera comment obtenir le HttpSession ou les propriétés individuelles.

J'ai omis les vérifications des limites null et index pour plus de concision.

Précautions

C'est délicat. La réponse de Martin Andersson n'est pas correcte car la même instance de ServerEndpointConfig.Configurator Est utilisée pour chaque connexion, donc une condition de concurrence existe. Alors que les documents indiquent que "l'implémentation crée une nouvelle instance du configurateur par point de terminaison logique", la spécification ne définit pas clairement un "point de terminaison logique". Sur la base du contexte de tous les endroits où cette phrase est utilisée, elle semble signifier la liaison d'une classe, d'un configurateur, d'un chemin et d'autres options, c'est-à-dire un ServerEndpointConfig, qui est clairement partagé. Quoi qu'il en soit, vous pouvez facilement voir si une implémentation utilise la même instance en imprimant sa toString() depuis modifyHandshake(...).

Plus surprenant, la réponse de Joakim Erdfelt ne fonctionne pas non plus de manière fiable. Le texte de JSR 356 lui-même ne mentionne pas EndpointConfig.getUserProperties(), il est uniquement dans JavaDoc, et nulle part il ne semble être spécifié quelle est sa relation exacte avec Session.getUserProperties(). En pratique, certaines implémentations (par exemple, Glassfish) renvoient la même instance Map pour tous les appels à ServerEndpointConfig.getUserProperties() tandis que d'autres (par exemple, Tomcat 8) ne le font pas. Vous pouvez vérifier en imprimant le contenu de la carte avant de le modifier dans modifyHandshake(...).

Pour vérifier, j'ai copié le code directement à partir des autres réponses, puis l'ai testé contre un client multithread que j'ai écrit. Dans les deux cas, j'ai observé que la session incorrecte était associée à l'instance de point de terminaison.

Aperçu des solutions

J'ai développé deux solutions dont j'ai vérifié le bon fonctionnement lors d'un test sur un client multithread. Il existe deux astuces clés.

Tout d'abord, utilisez un filtre avec le même chemin d'accès que le WebSocket. Cela vous donnera accès aux HttpServletRequest et HttpSession. Cela vous donne également la possibilité de créer une session si elle n'existe pas déjà (bien que dans ce cas, l'utilisation d'une session HTTP semble douteuse).

Deuxièmement, recherchez certaines propriétés qui existent à la fois dans WebSocket Session et HttpServletRequest ou HttpSession. Il s'avère qu'il y a deux candidats: getUserPrincipal() et getRequestParameterMap(). Je vais vous montrer comment abuser des deux :)

Solution utilisant le principal d'utilisateur

La manière la plus simple est de profiter de Session.getUserPrincipal() et HttpServletRequest.getUserPrincipal(). L'inconvénient est que cela pourrait interférer avec d'autres utilisations légitimes de cette propriété, alors n'utilisez-la que si vous êtes prêt à ces implications.

Si vous souhaitez stocker une seule chaîne, comme un ID utilisateur, ce n'est en fait pas trop abusif, bien qu'il devrait probablement être défini d'une manière gérée par conteneur plutôt que de remplacer le wrapper comme je vais vous le montrer. Quoi qu'il en soit, vous feriez cela en remplaçant simplement Principal.getName(). Ensuite, vous n'avez même pas besoin de le convertir dans le Endpoint. Mais si vous pouvez l'estimer, vous pouvez également passer tout l'objet HttpSession comme suit.

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

Solution utilisant des paramètres de demande

La deuxième option utilise Session.getRequestParameterMap() et HttpServletRequest.getParameterMap(). Notez qu'il utilise ServerEndpointConfig.getUserProperties() mais il est sûr dans ce cas car nous mettons toujours le même objet dans la carte, donc qu'il soit partagé ne fait aucune différence. L'identifiant de session unique est transmis non pas via les paramètres utilisateur mais via les paramètres de requête, qui est unique par requête.

Cette solution est légèrement moins hacky car elle n'interfère pas avec la propriété principale de l'utilisateur. Notez que si vous devez passer par les paramètres de demande réels en plus de celui qui est inséré, vous pouvez facilement le faire: commencez simplement par la mappe de paramètres de demande existante au lieu d'une nouvelle carte vide comme indiqué ici . Mais veillez à ce que l'utilisateur ne puisse pas usurper le paramètre spécial ajouté dans le filtre en fournissant son propre paramètre de demande du même nom dans la demande HTTP réelle.

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

Solution pour les propriétés uniques

Si vous n'avez besoin que de certaines propriétés de HttpSession et non de l'ensemble HttpSession lui-même, comme par exemple un ID utilisateur, alors vous pouvez supprimer l'ensemble SessionTracker de l'entreprise et simplement mettre les paramètres nécessaires dans la carte que vous revenez de votre remplacement de HttpServletRequestWrapper.getParameterMap(). Ensuite, vous pouvez également vous débarrasser du Configurator personnalisé; vos propriétés seront facilement accessibles à partir de Session.getRequestParameterMap() dans le point de terminaison.

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);
    }
}
17
Chad N B

C'est possible?

Examinons la spécification API Java pour WebSocket pour voir s'il est possible de récupérer l'objet HttpSession. spécification dit à la page 29:

Dans la mesure où les connexions Websocket sont lancées avec une demande http, il existe une association entre la session HttpSession sous laquelle un client fonctionne et toutes les connexions Web établies dans cette session HttpSession. L'API permet l'accès dans la poignée de main d'ouverture à la session HttpSession unique correspondant à ce même client.

Alors oui c'est possible.

Cependant, je ne pense pas qu'il soit possible pour vous d'obtenir une référence à l'objet HttpServletRequest. Vous pouvez écouter toutes les nouvelles demandes de servlet à l'aide d'un ServletRequestListener , mais vous devrez encore comprendre quelle demande appartient à quel point de terminaison de serveur. Veuillez me faire savoir si vous trouvez une solution!

Mode d'emploi abstrait

Le mode d'emploi est décrit de manière approximative aux pages 13 et 14 de la spécification et illustré par moi dans le code sous l'en-tête suivant.

En anglais, nous devrons intercepter le processus de prise de contact pour mettre la main sur un objet HttpSession. Pour transférer ensuite la référence HttpSession à notre point de terminaison de serveur, nous devons également intercepter lorsque le conteneur crée l'instance de point de terminaison de serveur et injecter manuellement la référence. Nous faisons tout cela en fournissant notre propre ServerEndpointConfig.Configurator Et en remplaçant les méthodes modifyHandshake() et getEndpointInstance().

Le configurateur personnalisé sera instancié une fois par ServerEndpoint logique (Voir JavaDoc ).

Exemple de code

Il s'agit de la classe de point de terminaison du serveur (je fournis l'implémentation de la classe CustomConfigurator après cet extrait de code):

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

Et voici le configurateur personnalisé:

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;
    }
}
7
Martin Andersson

Toutes les réponses ci-dessus méritent d'être lues, mais aucune d'entre elles ne résout le problème de OP (et mon).

Vous pouvez accéder à HttpSession lorsqu'un point de terminaison WS s'ouvre et le transmettre à une instance de point de terminaison nouvellement créée, mais personne ne garantit l'existence d'une instance HttpSession!

Nous avons donc besoin de l'étape 0 avant ce piratage (je déteste l'implémentation JSR 365 de WebSocket). Websocket - httpSession renvoie null

4
9dan

La seule façon qui fonctionne sur tous les serveurs d'applications est d'utiliser ThreadLocal. Voir:

https://Java.net/jira/browse/WEBSOCKET_SPEC-235

0
Ryan

Toutes les solutions possibles sont basées sur:

A. Les implémentations du navigateur client conservent l'ID de session via la valeur de cookie transmise en tant qu'en-tête HTTP ou (si les cookies sont désactivés), il est géré par le conteneur Servlet qui générera le suffixe d'ID de session pour les URL générées

B. Vous ne pouvez accéder aux en-têtes de demande HTTP que pendant la négociation HTTP; après cela, c'est le protocole Websocket

Pour que...

Solution 1: utilisez la "poignée de main" pour accéder à HTTP

Solution 2: dans votre JavaScript côté client, générez dynamiquement le paramètre ID de session HTTP et envoyez le premier message (via Websocket) contenant cet ID de session. Connectez "endpoint" à la classe de cache/utilitaire en conservant l'ID de session -> Mappage de session; pour éviter les fuites de mémoire, vous pouvez utiliser Session Listener par exemple pour supprimer la session du cache.

P.S. J'apprécie les réponses de Martin Andersson et Joakim Erdfelt. Malheureusement, la solution de Martin n'est pas sûre pour les threads ...

0
Fuad Efendi