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.
- Default behavior of the JSR, where each incoming upgrade request results in a new object instance of the endpoint class
- 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.
- Manually provided by the
javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
- Usually done within a
javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce)
call
- From the
javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set)
call.
- 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.
- does the path match any of the ServerEndpointConfig.getPath() entries
- If no match, return 404 to upgrade
- pass upgrade request into ServerEndpointConfig.Configurator.checkOrigin()
- If not valid, return error to upgrade response
- create HandshakeResponse
- pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedSubprotocol()
- store answer in HandshakeResponse
- pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedExtensions()
- store answer in HandshakeResponse
- Create new endpoint specific ServerEndpointConfig object. copy encoders, decoders, and User Properties. This new ServerEndpointConfig wraps default for path, extensions, endpoint class, subprotocols, configurator.
- pass upgrade request, response, and new ServerEndpointConfig into ServerEndpointConfig.Configurator.modifyHandshake()
- call ServerEndpointConfig.getEndpointClass()
- use class on ServerEndpointConfig.Configurator.getEndpointInstance(Class)
- create Session, associate endpoint instance and EndpointConfig object.
- Inform endpoint instance of connect
- annotated methods that want EndpointConfig gets the one associated with this Session.
- 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)