Netty: A Different Kind of Web(Socket) Server
Netty is used today in all kinds of applications, all over the Internet, to handle thousands (if not millions) of chat conversations, multiplayer games including Minecraft, Twitter, and many other applications. However, it hasn’t made it very far into the mindshare of enterprise programmers developing business applications.
I believe that Netty can introduce a new wave of functionality that other solutions simply cannot match because of its fully bi-directional text and binary non-HTTP data transport, along with its support for many more concurrent clients than traditional “thread-per-socket” servers.
You may know about Netty’s prowess with WebSockets, but did you know it can function extremely well as a traditional web server? Due to its very thoughtful design, by adding appropriate handlers to its pipeline, Netty can handle virtually any traffic. It can also handle multiple types concurrently, such as WebSockets and HTTP over the same port at the same time. By combining these together, programmers are spared from dealing with nuisances such as CORS (Cross Origin Resource Sharing) that can rear their ugly head when a browser tries to make requests to servers it did not download from.
The Power of Netty
To give an inkling of its power to transform enterprise applications, I put together a code example showing one of the traditional examples of the web, which is retrieving stock prices.
Other applications would have to make AJAX requests, poll, have refresh buttons, etc. to update prices. WebSockets removes the need for any of that. After creating a constantly open bi-directional connection, both the client and the server can talk to each other whenever there is a need, without any negotiation. So, the client lets the server know when any user alters criteria, and the server updates the client whenever relevant data changes based on that criteria.
- You can find the fully functional code here.
I’ve set up a little JSON-based protocol for the client to let the server know what the user has decided. To add a new symbol to the list that the server is watching for the client, a simple call is all that is necessary. Here’s an example:
doSend('{"command":"add", "tickerSymbol":"GOOG"}');
This adds the symbol to the list. The next update from the server includes the current stock price (from Yahoo Finance’s REST API) for the new symbol in its data. It is equally easy to remove an item:
doSend('{"command":"remove", "tickerSymbol":"GOOG"}');
With these two commands, the client controls the list of symbols the server is watching for each user. On the server-side in the Netty handler, the only thing the programmer has to do to account for multiple users is ensure that a new handler is created for each new connection, and that no static members are used where data is not to be shared. Unless told otherwise with an annotation, Netty assumes that handlers are not shareable.
Let’s look at how the handlers are defined to the Netty pipeline. This is from the StockTickerServer class:
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("encoder", new HttpResponseEncoder()); p.addLast("decoder", new HttpRequestDecoder()); p.addLast("aggregator", new HttpObjectAggregator(65536)); p.addLast("handler", new StockTickerServerHandler()); } });
The order here is very important, as each handler in the pipeline has a chance to process (or not process) data and pass it on to the next handler. The stock ticker handler is at the bottom, as it is the one that sends data back to the client and is therefore at the end of the pipeline. By creating new instances of the handlers, each new connection gets its own instances of each handler. If handlers are stateless and thread safe, singletons can be used instead where applicable to save memory. None of the handlers I’m using are shareable, so I’m not showing an example of that.
Netty as a Web Server
A few tricks are used to get Netty to handle HTTP and WebSocket traffic at the same time.
1. StockTickerServerHandler extends SimpleChannelInboundHandler < Object >
This tells Netty that we want all traffic to come to this handler. Otherwise, we could use SimpleChannelInboundHandler<FullHttpRequest> if we only wanted to handle HTTP traffic, or SimpleChannelInboundHandler<WebSocketFrame> if we only wanted to handle WebSocket traffic.
2. The channelRead0 (channel read zero) method
@Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { this.handleHttpRequest(ctx, (FullHttpRequest)msg); } else if (msg instanceof WebSocketFrame) { this.handleWebSocketFrame(ctx, (WebSocketFrame)msg); } }
This allows us to process HTTP and WebSocket traffic according to each protocol. handleHttpRequest serves up HTML, images, CSS, JavaScript, and all the other normal web traffic, and handleWebSocketFrame figures out what to do with the custom messages that are sent from the client.
3. Mime types
Netty doesn’t come built-in with support for processing mime types, as WebSocket calls don’t inherently need them.
I added a slightly modified version of Apache’s mime types file and load it statically. I’m synchronizing on the load because Netty can create quite a lot of handlers at the start for a pool if it wants to, and the constructor can be being executed by many handlers at the same time. Since the field is static, the map could be loaded many times before it becomes non-null. Synchronizing on a static lock (NOT the current instance of the class) prevents this from happening.
Other Details
The handleWebSocketFrame method takes care of the different “known” types of Frames that the WebSocket protocol defines. Once a full text frame is received, I pass it off to the implementor of the Interface I created to specify how to deal with the business logic.
That code lives in StockTickerMessageHandler. It creates a background thread to retrieve the stock quotes and send them to the client, and processes the commands that are sent by the client.
There is a bit of messy code in there for handling the Gzip compressed data sent by Yahoo and parsing the JSON returned by the service, along with some code that uses java.util.concurrent classes like Executor, AtomicBoolean, AtomicReference, and CopyOnWriteArrayList to keep the background thread and the Netty handler from stomping on each other as they share the details about the channel and the current list of symbols.
I’m also using Gson to turn the incoming JSON into POJOs so they are more easily processed. Other than that, it is just the business end of this example.
A Note About Authentication
I didn’t have time to add authentication to this example. If I did, I would have used Shiro, a super powerful authentication/authorization/cipher framework that works with both normal applications and web applications. HTTPS support is also lacking as this is a public application for checking stock prices. There is an example for adding HTTPS (and WSS) here.
One thing that is very difficult (if not impossible) with JavaScript WebSockets is sending authentication data along with the upgrade request (i.e. calling new WebSocket(uri)). For this reason, it is typical to first send a HTTPS POST like a normal website would and set an auth cookie token. That way, when the upgrade request is sent, the cookie is automatically sent along with it. When using authentication, remember to use HTTPS and WSS instead of HTTP and WS to protect data. Once the authentication is in place, it just becomes a matter of checking for the authenticated user where necessary, noting that some traffic should always pass through (HTML, images, etc.).
Conclusion
Netty has come into its own as a high performance, game changing way to build new applications. Today’s enterprise applications can be much more interactive than they are now by utilizing the capabilities offered by WebSockets. I hope you’ve enjoyed this little adventure into Netty, and please forgive the awful browser client, I just didn’t have the time to do a nice Backbone.js client app for this example.
Thanks!
Reference: | Netty: A Different Kind of Web(Socket) Server from our JCG partner John Boardman at the Keyhole Software blog. |