WebSocket Development With Java And Jetty
Recently at work we have been using WebSockets to communicate between our microservices and our service registries. In order to teach these concepts to others on my team, I decided to create some example projects on GitHub which show how easy it is to create WebSocket servers AND clients in Java.
Let begin with the server as it requires the most work. Jetty is an API, Server, and framework for creating HTTP services in Java. Since 9.1.x it has included an implementation of WebSockets for both clients and servers. You can have Jetty run as a servlet container which runs a WAR file or you can also embed Jetty into a JavaSE application and configure it programmatically. In order to avoid unneccessary XML, I like to use embedded Jetty as I find it to be simpler.
To start with, you will need to create an event handler class which will take care of all of the WebSocket events generated. These events are: OPEN, MESSAGE, ERROR, CLOSE. Here's what mine looks like:
You'll note that there are methods for each type of event: onConnect, onMessage, onError, onClose. And each has the appropriate WebSocket annotation above them: @OnWebSocketConnect, @OnWebSocketMessage, @OnWebSocketError, @OnWebSocketClose. Additionally, the class itself is annotated with @WebSocket. This is how Jetty is able to wire up the class to the events to be handled. Overall, VERY simple to implement. For now, just ignore the private class RandomIntervalMessageSender, we'll come back to it later.
If your event handler class doesn't need any dependencies (like mine has the Configuration object) you can skip to the next step. If you do need to put dependencies into your handler class when it gets created, you will need a WebSocketCreator class as shown below:
The purpose of this class is simply to make it possible to do more than just create an instance of your handler with no dependencies. If you need to pass in constructor arguments or whatnot, you will need as class like this. All that you have to do is return an instance of your event handler class however you want to configure it.
The third step in our journey is to create a WebSocketServlet which will use a new instance of our event handler class for each new WebSocket connection. This class is also quite simple and straightforward:
The idle timeout setting is there to allow you to set a maximum time (in milliseconds) for which a WebSocket connection can remain idle before the server will terminate the connection.
The next issue to grapple with is the creation of the Main class and using it to start Jetty. My main class is a little more complicated that the simple use case because I am parsing command-line arguments into a Configuration class. Even taking that into account, the class below is pretty easy to grok.
In the main() method, we parse the arguments and store them into a Configuration object. Those settings are then used to create an instance of a Jetty Server, add a ServletContextHandler, and assign our WebSocketServlet to the ServletContextHandler at a given endpoint.
If you're interested in the command-line arguments or running the whole application, you can see the description in the README.
That's all there is to the WebSocket server except for that embedded class I told you to ignore earlier.. Look back at the first listing above and you will see a nested class called RandomIntervalMessageSender. It implements java.lang.Thread and it is used to spawn a separate thread which sleeps for a random number of milliseconds and sends a WebSocket message which is a JSON string containing the current system time in milliseconds since the beginning of the UNIX epoch. The thread gets started from the onConnect method after confirming that the WebSocket connection is open.
WebSocket Client
The client application is even simpler and consists of only 3 classes. One Main class, one event handler class, and a Configuration class for holding configuration options. The event handler is VERY similar to the one in the server. It uses the same method names and the same annotations:
All that this client event handler will do is log any messages and events that it receives. It does no useful work, but it could easily be modified to add features. That discussion is beyond the scope of this post.
The Main class again has a configuration parser, but this time there is no need for a full-blown Jetty server because the client does not require it. We just have to instantiate the WebSocketClient (The SslContextFactory option allows us to accept self-signed certificates for SSL). Then we create an instance of URI which has the address of the WebSocket server we wish to talk to (in the format of ws://host:port/path/to/socket for unencrypted or wss://host:port/path/to/socker for SSL). My application provides the ability to pull in additional headers from a file for testing purposes (think Authorization or Cookie headers). Finally, we call the WebSocketClient.connect() method to bind the client and the event handler for the URI.
Wow! That was pretty simple! Just remember that your messages can also be in binary and I did not cover how to set up your event handler for binary messages, but more information about the Jetty WebSocket API can be found HERE. The full client code can be found HERE.
If you found this post useful, share it and let me know in the comments!
Comments