This blog post will be little more technical and insightful into Tyrus internals and won’t be as useful for common users which are just trying to use it for creating their web socket endpoint. I will describe how you can run Tyrus client or server side on virtually any container with a little help. We will start with Client side – it is a little bit simpler I think – then go through Server side and at the end I will present simple in-memory container which is already present in Tyrus workspace and is part of latest release (1.3.3).
Let me start with little background. Tyrus originally started as something built on top of project Grizzly, which serves as NIO layer in Glassfish. Grizzly had (still has) its own API which supports creating web socket endpoints and does even have client implementation. Then JSR 356 was started, Tyrus (originally called “websocket-sdk“) was created and it was just a simple adaptation layer on top of Grizzly container. Then it was decided that this JSR will be part of Java EE 7. This brought much more visibility to this project and also some more requirements. Tyrus was required to run on any Java EE 7 compliant container, utilising Servlet 3.1 API (upgrade mechanism, non-blocking reads and writes). So another layer was created, this time slightly more low level, because Tyrus was required to handle reading and writing, something which was previously handled by Grizzly. The decision was made, Tyrus adopted some Grizzly code and the Container SPI started to form. Tyrus still kept Grizzly container support and it was able to work with Servet 3.1 API as well. (Just for the sake of completeness – this is about server side. Client part always exclusively used Grizzly as container).
Then another requirement came – it was decided that Tyrus will be integrated into WebLogic server (it will be part of upcoming 12.1.3 release). Initial integration used lots of Tyrus internals which was kind of scary, because future integrations might require significant amount of time, especially when considering some not trivial features we plan for Tyrus (Extension support, subprotocols, etc). WebLogic 12.1.3 does not contain Grizzly nor Servlet 3.1 implementation, so we needed to have something which will be more stable than any other Tyrus internal class.
Small note – this is final version of the SPI for now, it does not mean that it won’t change. We might need to extend it to support some new features or fix bugs. Also there is no discovery mechanism in place (yet), so implementors of these SPI will have direct dependency on some Tyrus internal class(es). These internal classes (TyrusWebSocketEngine, etc.) may be changed, but there is higher resistance mode for these, so it shouldn’t be done that often and if we can achieve backwards compatibility, we will. Discovery mechanism will most likely be introduced sometime later..
Enough with this history lesson, let’s get to the code part.
Client side container is basically only about implementing ClientContainer. Method openClientSocket will be called when ClientManager needs to connect to server endpoint and following sequence of calls is then expected:
That’s it. ReadHandler and Writer classes are pretty straightforward, so no need to go there, only last important thing is how we can register ClientContainer so the runtime will use it. Here you need to implement ContainerProvider from JSR-356 API:
Note “registered via META-INF/services” means that you need to provide file named “javax.websocket.ContainerProvider” under META-INF/services directory and put fully classified class name of your implementation. Then all applications which run “ContainerProvider.getWebSocketContainer();” will get the client using your ClientContainer to make connections. You can always just use ClientManager directly if you need (it implements WebSocketContainer, so it is only about modifying the initial client instantiation).
Server side is slightly more complicated for obvious reasons – server bootstrap is more complicated – more than one endpoint can be deployed. Also it needs to handle more than one connection and there are some features defined in JSR 356 which complicate things a little bit.
Firstly you need to create ServerContainer. Tyrus has its own implementation – TyrusServerContainer. It has two abstract methods which needs to be implemented. Tyrus also comes to help you – these methods are implemented in TyrusWebSocketEngine, so you most likely will create instance of this class for your ServerContainer descendant. Then you are ready to start created container and you can accept incoming connections.
ServerContainer and WebSocketEngine instance you created should be used during whole life of deployed application. All objects created from this point on are related directly to ongoing connection / web socket session app.
WebSocketEngine has upgrade method which will validate incoming HTTP request and creates outgoing response. It returns UpgradeInfo, which has status (HANSDHAKE_FAILED, NOT_APPLICABLE, SUCCESS) and can create connection in case status is SUCCESS. HANDSHAKE_FAILED means there were some errors in HTTP header, NOT_APPLICABLE means that passed request is not upgrade request and you might want to pass it to further in the processing chain. For creating a connection, you need to have Writer implementation (very same interface is used on client side). Then the processing is simple – you just read the data from the connection, call connection.getReadHandler().handle(ByteBuffer), data is written through provided Writer implementation. And that’s pretty much it.
Sample code for this is not as simple as in client case and there would be left-out things, so lets skip it in this section. InMemory container implements both client and server in the easiest way possible, so if you are still interested, just continue reading.
This is nothing else than a simple test for both SPIs. It can be used for fast unit/e2e testing if you don’t need anything else than Tyrus runtime and you are fine that each connectToServer method call will create its own “server” container (you cannot connect from two clients to one in-memory server).
The best way how to explore that code would be check it out, open in your favourite IDE. You should be also able to see javadocs and maybe start debugging session to really see what is called when and so on.
Links to InMemory container implementation:
And I think that was enough for this post. Feel free to ask about whatever you want to know about Tyrus or WebSocket implementation. As almost always, you can find list of relevant links at the very bottom.