Acessando HttpSession de HttpServletRequest em um soquete da Web @ServerEndpoint

É possível obter o HttpServletRequest dentro de um @ServerEndpoint? Principalmente eu estou tentando obtê-lo para que eu possa acessar o object HttpSession.

Atualização (novembro de 2016) : As informações fornecidas nesta resposta são para a especificação JSR356, e as implementações individuais da especificação podem variar fora dessas informações. Outras sugestões encontradas nos comentários e outras respostas são todos os comportamentos específicos da implementação fora da especificação JSR356.

Se as sugestões aqui estiverem causando problemas, atualize suas várias instalações do Jetty, Tomcat, Wildfly ou Glassfish / Tyrus. Todas as versões atuais dessas implementações foram todas relatadas para funcionar da maneira descrita abaixo.

Agora de volta à resposta original de agosto de 2013

A resposta de Martin Andersson tem uma falha de concorrência. O configurador pode ser chamado por vários segmentos ao mesmo tempo, é provável que você não terá access ao object HttpSession correto entre as chamadas de modifyHandshake() e getEndpointInstance() .

Ou disse outro jeito …

  • Solicitar A
  • Modificar o handshake A
  • Solicitar B
  • Modifique o handshake B
  • Obter Instância de Ponto de Extremidade A < - isso teria a HttpSession da Solicitação B
  • Obter a instância de endpoint B

Aqui está uma modificação no código de Martin que usa o mapa ServerEndpointConfig.getUserProperties() para disponibilizar o HttpSession para sua instância de soquete durante a chamada do 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); } } 

Recurso de bônus: nenhum instanceof casting ou requerido.

Alguns conhecimentos do EndpointConfig

Objetos EndpointConfig existem por “Endpoint Instance”.

No entanto, uma “Instância de terminal” tem dois significados com a especificação.

  1. Comportamento padrão do JSR, em que cada solicitação de upgrade de input resulta em uma nova instância de object da class de terminal
  2. Um javax.websocket.Session que vincula a instância de terminal do object, com sua configuração, a uma conexão lógica específica.

É possível ter uma instância de ponto de extremidade singleton sendo usada para várias instâncias javax.websocket.Session (que é um dos resources que ServerEndpointConfig.Configurator suporta)

A implementação do ServerContainer rastreará um conjunto de ServerEndpointConfig que representam todos os pontos de extremidade implantados que o servidor pode responder a uma solicitação de upgrade do websocket.

Essas instâncias de object ServerEndpointConfig podem vir de algumas fonts diferentes.

  1. Manualmente fornecido pelo javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
    • Geralmente feito dentro de uma chamada javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce)
  2. Na javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set) .
  3. Criada automaticamente a partir da varredura do aplicativo da web para classs anotadas do @ServerEndpoint .

Essas instâncias de object ServerEndpointConfig existem como padrões para quando um javax.websocket.Session eventualmente é criado.

Instância ServerEndpointConfig.Configurator

Antes que quaisquer solicitações de upgrade sejam recebidas ou processadas, todos os objects ServerEndpointConfig.Configurator agora existem e estão prontos para executar sua finalidade principal e única, para permitir a personalização do processo de upgrade de uma conexão de websocket para um eventual javax.websocket.Session

Acesso ao EndpointConfig específico da session

Observe que você não pode acessar as instâncias do object ServerEndpointConfig de dentro de uma instância de terminal. Você só pode acessar instâncias do EndpointConfig .

Isso significa que, se você forneceu ServerContainer.addEndpoint(new MyCustomServerEndpointConfig()) durante a implantação e depois tentou acessá-lo por meio das annotations, ele não funcionará.

Todos os itens a seguir seriam inválidos.

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

Você pode acessar o EndpointConfig durante a vida da instância do object Endpoint, mas em um tempo limitado. Os javax.websocket.Endpoint.onOpen(Session,Endpoint) , @OnOpen anotados ou através do uso de CDI. O EndpointConfig não está disponível de nenhuma outra maneira ou em qualquer outro momento.

No entanto, você sempre pode acessar o UserProperties através da chamada Session.getUserProperties() , que está sempre disponível. Este mapa de Propriedades do Usuário está sempre disponível, seja por meio das técnicas anotadas (como um parâmetro Session durante as @OnOpen , @OnClose , @OnError ou @OnMessage ), via injeção CDI da Sessão, ou até mesmo com o uso de WebSockets anotados que se estendem de javax.websocket.Endpoint .

Como funciona o upgrade

Como dito anteriormente, cada um dos terminais definidos terá um ServerEndpointConfig associado a ele.

Aqueles ServerEndpointConfigs são uma única instância que representa o estado padrão do EndpointConfig que eventualmente são disponibilizados para as Instâncias de Endpoint que são possivelmente e eventualmente criadas.

Quando uma solicitação de atualização recebida chega, ela passa pelo seguinte no JSR.

  1. o caminho corresponde a qualquer uma das inputs ServerEndpointConfig.getPath ()
    • Se não houver correspondência, retorne 404 para atualizar
  2. passar solicitação de upgrade para ServerEndpointConfig.Configurator.checkOrigin ()
    • Se não for válido, retorne o erro para atualizar a resposta
    • criar HandshakeResponse
  3. passe o pedido de upgrade para o ServerEndpointConfig.Configurator.getNegotiatedSubprotocol ()
    • armazenar resposta no HandshakeResponse
  4. passar solicitação de upgrade para ServerEndpointConfig.Configurator.getNegotiatedExtensions ()
    • armazenar resposta no HandshakeResponse
  5. Crie um novo object ServerEndpointConfig específico do nó de extremidade. copiar codificadores, decodificadores e propriedades do usuário. Esse novo ServerEndpointConfig envolve o padrão para caminho, extensões, class de terminal, subprotocols, configurador.
  6. passar solicitação de upgrade, resposta e novo ServerEndpointConfig em ServerEndpointConfig.Configurator.modifyHandshake ()
  7. chamar ServerEndpointConfig.getEndpointClass ()
  8. use class no ServerEndpointConfig.Configurator.getEndpointInstance (Class)
  9. crie Session, associe a instância do endpoint e o object EndpointConfig.
  10. Informar a instância do terminal de conexão
  11. methods anotados que desejam que o EndpointConfig obtenha aquele associado a esta Sessão.
  12. as chamadas para Session.getUserProperties () retornam EndpointConfig.getUserProperties ()

Para observar, o ServerEndpointConfig.Configurator é um ponto de extremidade ServerContainer mapeado por singleton.

Isso é intencional e desejado, para permitir aos implementadores vários resources.

  • para retornar a mesma instância de Ponto de Extremidade para vários peers, se assim o desejarem. A chamada abordagem sem estado para escrever websocket.
  • ter um ponto único de gerenciamento de resources caros para todas as instâncias do Endpoint

Se as implementações criassem um novo Configurador para cada handshake, essa técnica não seria possível.

(Divulgação: escrevo e mantenho a implementação da JSR-356 para o Jetty 9)

Prefácio

Não está claro se você está querendo o HttpServletRequest , o HttpSession ou propriedades fora do HttpSession . Minha resposta mostrará como obter o HttpSession ou propriedades individuais.

Eu omiti as verificações de limites de índice e nulo por brevidade.

Cuidados

É complicado. A resposta de Martin Andersson não está correta porque a mesma instância de ServerEndpointConfig.Configurator é usada para cada conexão, portanto, existe uma condição de corrida. Enquanto os documentos afirmam que “A implementação cria uma nova instância do configurador por terminal lógico”, a especificação não define claramente um “terminal lógico”. Com base no contexto de todos os lugares em que a frase é usada, parece significar a binding de uma class, configurador, caminho e outras opções, isto é, um ServerEndpointConfig , que é claramente compartilhado. De qualquer forma, você pode ver facilmente se uma implementação está usando a mesma instância imprimindo seu toString() dentro de modifyHandshake(...) .

Mais surpreendentemente a resposta de Joakim Erdfelt também não funciona de forma confiável. O texto do JSR 356 em si não menciona EndpointConfig.getUserProperties() , é apenas no JavaDoc, e em nenhum lugar parece ser especificado qual é o seu relacionamento exato com Session.getUserProperties() . Na prática, algumas implementações (por exemplo, Glassfish) retornam a mesma instância do Map para todas as chamadas para ServerEndpointConfig.getUserProperties() enquanto outras (por exemplo, Tomcat 8) não. Você pode verificar imprimindo o conteúdo do mapa antes de modificá-lo em modifyHandshake(...) .

Para verificar, copiei o código diretamente das outras respostas e testei-o em um cliente multithread que eu escrevi. Em ambos os casos, observei a session incorreta sendo associada à instância do terminal.

Esboço de Soluções

Desenvolvi duas soluções, que verifiquei funcionam corretamente quando testadas em um cliente multithread. Existem dois truques fundamentais.

Primeiro, use um filtro com o mesmo caminho que o WebSocket. Isso lhe dará access ao HttpServletRequest e HttpSession . Ele também lhe dá a chance de criar uma session se ela ainda não existir (embora, nesse caso, usar uma session HTTP pareça duvidosa).

Segundo, encontre algumas propriedades que existem na Session WebSocket e HttpServletRequest ou HttpSession . Acontece que existem dois candidatos: getUserPrincipal() e getRequestParameterMap() . Eu vou te mostrar como abusar dos dois 🙂

Solução usando o Principal do usuário

A maneira mais fácil é aproveitar o Session.getUserPrincipal() e o HttpServletRequest.getUserPrincipal() . A desvantagem é que isso poderia interferir com outros usos legítimos dessa propriedade, portanto, use-a apenas se você estiver preparado para essas implicações.

Se você quiser armazenar apenas uma string, como um ID do usuário, isso não é muito abuso, embora presumivelmente deva ser definido de alguma maneira gerenciada por contêiner, em vez de replace o wrapper como mostrarei. De qualquer forma você faria isso simplesmente sobrescrevendo Principal.getName() . Então você nem precisa lançá-lo no Endpoint . Mas se você aguentar, você também pode passar todo o object HttpSession seguinte maneira.

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

Solução Usando parameters de Solicitação

A segunda opção usa Session.getRequestParameterMap() e HttpServletRequest.getParameterMap() . Note que ele usa ServerEndpointConfig.getUserProperties() mas é seguro neste caso porque estamos sempre colocando o mesmo object no mapa, portanto, se ele é compartilhado, não faz diferença. O identificador de session exclusivo é transmitido não pelos parâmetros do usuário, mas pelos parâmetros de solicitação, que são exclusivos por solicitação.

Essa solução é um pouco menos hacky porque não interfere na propriedade principal do usuário. Observe que, se você precisar passar pelos parâmetros de solicitação reais além do que está inserido, poderá fazê-lo facilmente: basta começar com o mapa do parâmetro de solicitação existente, em vez de um novo vazio, como mostrado aqui. Mas tome cuidado para que o usuário não possa falsificar o parâmetro especial incluído no filtro, fornecendo seu próprio parâmetro de solicitação com o mesmo nome na solicitação 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 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 fakedParams = Collections.singletonMap("sessionId", new String[] { httpRequest.getSession().getId() }); HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public Map 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); } } } 

Solução para propriedades únicas

Se você precisa apenas de determinadas propriedades do HttpSession e não de toda a HttpSession , como um ID de usuário, você pode acabar com todo o negócio do SessionTracker e apenas colocar os parâmetros necessários no mapa que você retorna da substituição do HttpServletRequestWrapper.getParameterMap() . Então você também pode se livrar do Configurator personalizado; suas propriedades serão convenientemente acessíveis a partir de Session.getRequestParameterMap() no 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 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 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> 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); } } 

É possível?

Vamos revisar a especificação da Java API for WebSocket para ver se a obtenção do object HttpSession é possível. A especificação diz na página 29:

Como as conexões do websocket são iniciadas com uma solicitação http, há uma associação entre o HttpSession no qual um cliente está operando e quaisquer websockets que são estabelecidos dentro desse HttpSession. A API permite access no handshake de abertura ao HttpSession exclusivo correspondente ao mesmo cliente.

Então sim, é possível.

No entanto, não creio que seja possível obter uma referência ao object HttpServletRequest . Você poderia ouvir todas as novas solicitações de servlet usando um ServletRequestListener , mas ainda teria que descobrir qual solicitação pertence a qual ponto de extremidade do servidor. Por favor, deixe-me saber se você encontrar uma solução!

Resumo como fazer

How-to é vagamente descrito nas páginas 13 e 14 na especificação e exemplificado por mim no código sob o próximo header.

Em inglês, precisaremos interceptar o processo de handshake para obter um object HttpSession . Para então transferir a referência HttpSession para nosso terminal do servidor, também precisamos interceptar quando o contêiner criar a instância do nó de extremidade do servidor e injetar manualmente a referência. Fazemos tudo isso fornecendo nosso próprio ServerEndpointConfig.Configurator e sobrescrevemos os methods modifyHandshake() e getEndpointInstance() .

O configurador customizado será instanciado uma vez por ServerEndpoint lógico (consulte o JavaDoc ).

Exemplo de código

Esta é a class de terminal do servidor (forneço a implementação da class CustomConfigurator após este snippet 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()); } } 

E este é o 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 getEndpointInstance(Class 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 as respostas acima merecem ser lidas, mas nenhuma delas resolve o problema do OP (e do meu).

Você pode acessar o HttpSession quando um ponto final do WS é aberto e passá-lo para a instância do ponto de extremidade recém-criada, mas ninguém garante que exista uma instância do HttpSession!

Então, precisamos do passo 0 antes desse hacking (eu odeio a implementação JSR 365 do WebSocket). Websocket – httpSession retorna null

Todas as soluções possíveis são baseadas em:

A. As implementações do navegador do cliente mantêm o ID da session por meio do valor do cookie transmitido como um header HTTP ou (se os cookies estiverem desativados) ele é gerenciado pelo contêiner do Servlet que gerará o postfix do ID da session para URLs gerados

B. Você pode acessar headers de solicitação HTTP somente durante o handshake HTTP; depois disso, é o protocolo Websocket

De modo a…

Solução 1: use “handshake” para acessar o HTTP

Solução 2: Em seu JavaScript no lado do cliente, gere dinamicamente o parâmetro ID de session HTTP e envie a primeira mensagem (via Websocket) contendo este ID de session. Conecte o “endpoint” à class de cache / utilitário mantendo a ID da session -> Mapeamento da session; Para evitar vazamentos de memory, você pode usar o Session Listener, por exemplo, para remover a session do cache.

PS Eu agradeço as respostas de Martin Andersson e Joakim Erdfelt. Infelizmente, a solução de Martin não é thread-safe …

A única maneira que funciona em todos os servidores de aplicativos é usar ThreadLocal. Vejo:

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