Accessing HttpSession from HttpServletRequest in a Web Socket @ServerEndpoint

Archimedes Trajano picture Archimedes Trajano · Jul 30, 2013 · Viewed 56.2k times · Source

Is it possible to get the HttpServletRequest inside a @ServerEndpoint? Primarily I am trying to get it so I can access the HttpSession object.

Answer

Joakim Erdfelt picture Joakim Erdfelt · Aug 1, 2013

Update (November 2016): The information provided in this answer is for the JSR356 spec, individual implementations of the spec may vary outside of this information. Other suggestions found in comments and other answers are all implementation specific behaviors outside of the JSR356 spec.

If the suggestions in here are causing you problems, upgrade your various installations of Jetty, Tomcat, Wildfly, or Glassfish/Tyrus. All current versions of those implementations have all been reported to work in the way outlined below.

Now back to the original answer from August 2013...

The answer from Martin Andersson has a concurrency flaw. The Configurator can be called by multiple threads at the same time, it is likely that you will not have access to the correct HttpSession object between the calls from modifyHandshake() and getEndpointInstance().

Or said another way...

  • Request A
  • Modify Handshake A
  • Request B
  • Modify Handshake B
  • Get Endpoint Instance A <-- this would have Request B's HttpSession
  • Get Endpoint Instance B

Here's a modification to Martin's code that uses ServerEndpointConfig.getUserProperties() map to make the HttpSession available to your socket instance during the @OnOpen method call

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 feature: no instanceof or casting required.

Some EndpointConfig Knowledge

EndpointConfig objects do exist per "Endpoint Instance".

However, an "Endpoint Instance" has 2 meanings with the spec.

  1. Default behavior of the JSR, where each incoming upgrade request results in a new object instance of the endpoint class
  2. A javax.websocket.Session that ties together the object endpoint instance, with its configuration, to a specific logical connection.

It is possible to have a singleton Endpoint instance being used for multiple javax.websocket.Session instances (that is one of the features that ServerEndpointConfig.Configurator supports)

The ServerContainer implementation will track a set of ServerEndpointConfig's that represent all of the deployed endpoints that the server can respond to a websocket upgrade request.

These ServerEndpointConfig object instances can come from a few different sources.

  1. Manually provided by the javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
    • Usually done within a javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce) call
  2. From the javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set) call.
  3. Automatically created from scanning of the web application for @ServerEndpoint annotated classes.

These ServerEndpointConfig object instances exist as defaults for when a javax.websocket.Session does eventually get created.

ServerEndpointConfig.Configurator Instance

Before any upgrade requests are received or processed, all of the ServerEndpointConfig.Configurator objects now exist and are ready to perform their main and sole purpose, to allow for customization of the upgrade process of a websocket connection to an eventual javax.websocket.Session

Access to Session specific EndpointConfig

Note, you cannot access the ServerEndpointConfig object instances from within a endpoint instance. You can only access EndpointConfig instances.

This means if you provided ServerContainer.addEndpoint(new MyCustomServerEndpointConfig()) during deploy and later tried to access it via the annotations, it will not work.

All of the following would be invalid.

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

You can access the EndpointConfig during the life of the Endpoint object instance, but under a limited time. The javax.websocket.Endpoint.onOpen(Session,Endpoint), annotated @OnOpen methods, or via the use of CDI. The EndpointConfig is not available in any other way or at any other time.

However, you can always access the UserProperties via the Session.getUserProperties() call, which is available always. This User Properties map is always available, be it via the annotated techniques (such as a Session parameter during @OnOpen, @OnClose, @OnError, or @OnMessage calls), via CDI injection of the Session, or even with the use of non-annotated websockets that extend from javax.websocket.Endpoint.

How Upgrade Works

As stated before, every one of the defined endpoints will have a ServerEndpointConfig associated with it.

Those ServerEndpointConfigs are a single instance that represents the default state of the EndpointConfig that are eventually made available to the Endpoint Instances that are possibly and eventually created.

When a incoming upgrade request arrives, it has go through the following on the JSR.

  1. does the path match any of the ServerEndpointConfig.getPath() entries
    • If no match, return 404 to upgrade
  2. pass upgrade request into ServerEndpointConfig.Configurator.checkOrigin()
    • If not valid, return error to upgrade response
    • create HandshakeResponse
  3. pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedSubprotocol()
    • store answer in HandshakeResponse
  4. pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedExtensions()
    • store answer in HandshakeResponse
  5. Create new endpoint specific ServerEndpointConfig object. copy encoders, decoders, and User Properties. This new ServerEndpointConfig wraps default for path, extensions, endpoint class, subprotocols, configurator.
  6. pass upgrade request, response, and new ServerEndpointConfig into ServerEndpointConfig.Configurator.modifyHandshake()
  7. call ServerEndpointConfig.getEndpointClass()
  8. use class on ServerEndpointConfig.Configurator.getEndpointInstance(Class)
  9. create Session, associate endpoint instance and EndpointConfig object.
  10. Inform endpoint instance of connect
  11. annotated methods that want EndpointConfig gets the one associated with this Session.
  12. calls to Session.getUserProperties() returns EndpointConfig.getUserProperties()

To note, the ServerEndpointConfig.Configurator is a singleton, per mapped ServerContainer endpoint.

This is intentional, and desired, to allow implementors several features.

  • to return the same Endpoint instance for multiple peers if they so desire. The so called stateless approach to websocket writing.
  • to have a single point of management of expensive resources for all Endpoint instances

If the implementations created a new Configurator for every handshake, this technique would not be possible.

(Disclosure: I write and maintain the JSR-356 implementation for Jetty 9)