Integrating CDI and WebSockets
Thought of experimenting with a simple Java EE 7 prototype application involving JAX-RS (REST), WebSockets and CDI.
Note: Don’t want this to be a spoiler – but this post mainly talks about an issue which I faced while trying to use web sockets and REST using CDI as a ‘glue’ (in a Java EE app). The integration did not materialize, but a few lessons learnt nonetheless :-)
The idea was to use a REST end point as a ‘feed’ for a web socket end point which would in turn ‘push’ data to all connected clients:
- JAX-RS end point which receives data (possibly in real time) from other sources as an input to the web socket end point
- Use CDI Events as the glue b/w JAX-RS and WebSocket end points and ‘fire’ the payload
@Path("/feed") public class RESTFeed { @Inject Event<String> event; @POST @Consumes(MediaType.TEXT_PLAIN) public void push(String msg) { event.fire(msg); } }
- Use a CDI Observer method in the WebSocket endpoint implementation to push data to connected clients:
public void onMsg(@Observes String msg) { //different WS enpoint instance - notice the hash code value in the server log System.out.println("WS End point class ID -- " + this.hashCode()); try { client.getBasicRemote().sendText(msg); } catch (IOException ex) { Logger.getLogger(ServerEndpoint.class.getName()).log(Level.SEVERE, null, ex); } }
Of course, finer details like performance, async communication etc have not being considered at this point of time. More of an experiment
But is this even possible ?
Here are the steps which I executed
- Deployed the code
- Browsed to http://localhost:8080/Explore-WebSocket-CDI-Integration-Maven/ and connected as a web socket client
- Fired a HTTP POST request on the REST end point using Postman
Boom! A NullPointerException in the Observer method – I waited for a few seconds and then reality hit me!
Root cause (from what I understand)
- Behavior of WebSocket end points
WebSocket end points are similar to JAX-RS resource classes in the sense that there is one instance of a web socket endpoint class per connected client (at least by default). This is clearly mentioned in the WebSocket specification. As soon as a client (peer) connects, a unique instance is created and one can safely cache the web socket Session object (representation of the peer) as an instance variable. IMO, this a simple and clean programming model
- But the CDI container had other plans !
As soon as the REST end point fires a CDI event (in response to a POST request), the CDI container creates a different instance of the WebSocket endpoint (the CDI Observer in this case). Why? Because CDI beans are contextual in nature. The application does not control the instances of CDI beans. It just uses them (via @Inject). Its up to the container to create and destroy bean instances and ensure that an appropriate instance is available to beans executing in the same context. How does the container figure out the context though ? It’s via Scopes – Application, Session, Request etc…..
(again, clearly mentioned in the CDI specification)
So, the gist of the matter is that there is NO instance of the WebSocket endpoint current context – hence a new instance is created by CDI in order to deliver the message. This of course means that the instance variable would point to null and hence the NPE (Duh !)
So the question is . . .
Which CDI scope is to be used for a WebSocket end point ??? I tried @ApplicationScoped, @SessionScoped and @RequestScoped without much luck – still a new instance and a NPE
Any other options ??
- Defining a Set of Session as static variable will do the trick:
private static Set<Session> peers = Collections.synchronizedSet(new HashSet());
But that IMO is a just a hack and not feasible in case one needs to handle client specific state (which can only be handled as instance variables) in the observer method – it’s bound to remain uninitialized
- Server Sent events ? But at the end of the day, SSE != WebSocket. In case the use case demands server side push ‘only’, one can opt for it. SSE is not a Java EE standard yet – Java EE 8 might make this possible
Solution ?
I am not an expert – but I guess it’s up to the WebSocket spec to provide more clarity on how to leverage it with CDI. Given that CDI is an indispensable part of the Java EE spec, it’s extremely important that it integrates seamlessly with other specifications – specially HTML5-centric specs such as JAX-RS, WebSocket etc
This post by Bruno Borges links to similar issues related to JMS, CDI and WebSocket and how they integrate with each other.
Did I miss something obvious? Do you have any inputs/solutions? Please feel free to chime in ! :-)
The sample code is available on GitHub (in case you want to take a look). I tried this on GlassFish 4.1 and Wildfly 8.2.0
That’s all for now I guess…. :-)
Cheers!
Reference: | Integrating CDI and WebSockets from our JCG partner Abhishek Gupta at the Object Oriented.. blog. |