Creating WebSockets in Jetty is even easier with Jetty 9!
While the networking gurus in Jetty have been working on the awesome improvements to the I/O layers in core Jetty 9, the WebSocket fanatics in the community have been working on making writing WebSockets even easier.
The initial WebSocket implementation in Jetty was started back in November of 2009, well before the WebSocket protocol was finalized.
It has grown in response to Jetty’s involvement with the WebSocket draft discussions to the finalization of RFC6455, and onwards into the changes being influenced on our design as a result of WebSocket extensions drafts such as x-webkit-perframe-deflate, permessage-deflate, fragment, and ongoing mux discussions.
The Jetty 7.x and Jetty 8.x codebases provided WebSockets to developers required a complex set of knowledge about how WebSockets work and how Jetty implemented WebSockets. This complexity was as a result of this rather organic growth of WebSocket knowledge around intermediaries and WebSocket Extensions impacted the original design.
With Jetty 9.x we were given an opportunity to correct our mistakes.
The new WebSockets API in Jetty 9.x
Note: this information represents what is in the jetty-9 branch on git, which has changed in small but important ways since 9.0.0.M0 was released.
With the growing interest in next generation protocols like SPDY and HTTP/2.0, along with evolving standards being tracked for Servlet API 3.1 and Java API for WebSockets (JSR-356), the time Jetty 9.x was at hand. We dove head first into cleaning up the codebase, performing some needed refactoring, and upgrading the codebase to Java 7.
Along the way, Jetty 9.x started to shed the old blocking I/O layers, and all of the nasty logic surrounding it, resulting on a Async I/O focused Jetty core. We love this new layer, and we expect you will to, even if you don’t see it directly. This change benefits Jetty with a smaller / cleaner / easier to maintain and test codebase, along with various performance improvements such as speed, CPU use, and even less memory use.
In parallel, the Jetty WebSocket codebase changed to soak up the knowledge gained in our early adoption of WebSockets and also to utilize the benefits of the new Jetty Async I/O layers better. It is important to note that Jetty 9.x WebSockets is NOT backward compatible with prior Jetty versions.
The most significant changes:
- Requires Java 7
- Only supporting WebSocket version 13 (RFC-6455)
- Artifact Split
The monolithic jetty-websocket artifact has been to split up the various websocket artifacts so that developers can pick and choose what’s important to them.
The new artifacts are all under the org.eclipse.jetty.websocket groupId on maven central.
websocket-core.jar
– where the basic API classes reside, plus internal implementation details that are common between server & client.websocket-server.jar
– the server specific classeswebsocket-client.jar
– the client specific classes
- Only 1 Listener now (WebSocketListener)
- Now Supports Annotated WebSocket classes
- Focus is on Messages not Frames
In our prior WebSocket API we assumed, incorrectly, that developers would want to work with the raw WebSocket framing. This change brings us in line with how every other WebSocket API behaves, working with messages, not frames.
- WebSocketServlet only configures for a WebSocketCreator
This subtle change means that the Servlet no longer creates websockets of its own, and instead this work is done by the WebSocketCreator of your choice (don’t worry, there is a default creator).
This is important to properly support the mux extensions and future Java API for WebSockets (JSR-356)
Jetty 9.x WebSockets Quick Start:
Before we get started, some important WebSocket Basics & Gotchas
- A WebSocket Frame is the most fundamental part of the protocol, however it is not really the best way to read/write to websockets.
- A WebSocket Message can be 1 or more frames, this is the model of interaction with a WebSocket in Jetty 9.x
- A WebSocket TEXT Message can only ever be UTF-8 encoded. (it you need other forms of encoding, use a BINARY Message)
- A WebSocket BINARY Message can be anything that will fit in a byte array.
- Use the WebSocketPolicy (available in the WebSocketServerFactory) to configure some constraints on what the maximum text and binary message size should be for your socket (to prevent clients from sending massive messages or frames)
First, we need the servlet to provide the glue.
We’ll be overriding the configure(WebSocketServerFactory) here to configure a basic MyEchoSocket to run when an incoming request to upgrade occurs.
package examples; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; import org.eclipse.jetty.websocket.server.WebSocketServlet; public class MyEchoServlet extends WebSocketServlet { @Override public void configure(WebSocketServerFactory factory) { // register a socket class as default factory.register(MyEchoSocket.class); } }
The responsibility of your WebSocketServlet class is to configure the WebSocketServerFactory. The most important aspect is describing how WebSocket implementations are to be created when request for new sockets arrive. This is accomplished by configuring an appropriate WebSocketCreator object. In the above example, the default WebSocketCreator is being used to register a specific class to instantiate on each new incoming Upgrade request.
If you wish to use your own WebSocketCreator implementation, you can provide it during this configure step.
Check the examples/echo to see how this is done with factory.setCreator() and EchoCreator.
Note that request for new websockets can arrive from a number of different code paths, not all of which will result in your WebSocketServlet being executed. Mux for example will result in a new WebSocket request arriving as a logic channel within the MuxExtension itself.
As for implementing the MyEchoSocket, you have 3 choices.
- Implementing Listener
- Using an Adapter
- Using Annotations
Choice 1: implementing WebSocketListener interface.
Implementing WebSocketListener is the oldest and most fundamental approach available to you for working with WebSocket in a traditional listener approach (be sure you read the other approaches below before you settle on this approach).
It is your responsibility to handle the connection open/close events appropriately when using the WebSocketListener. Once you obtain a reference to the WebSocketConnection, you have a variety of NIO/Async based write() methods to write content back out the connection.
package examples; import java.io.IOException; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.websocket.core.api.WebSocketConnection; import org.eclipse.jetty.websocket.core.api.WebSocketException; import org.eclipse.jetty.websocket.core.api.WebSocketListener; public class MyEchoSocket implements WebSocketListener { private WebSocketConnection outbound; @Override public void onWebSocketBinary(byte[] payload, int offset, int len) { /* only interested in text messages */ } @Override public void onWebSocketClose(int statusCode, String reason) { this.outbound = null; } @Override public void onWebSocketConnect(WebSocketConnection connection) { this.outbound = connection; } @Override public void onWebSocketException(WebSocketException error) { error.printStackTrace(); } @Override public void onWebSocketText(String message) { if (outbound == null) { return; } try { String context = null; Callback callback = new FutureCallback<>(); outbound.write(context,callback,message); } catch (IOException e) { e.printStackTrace(); } } }
Choice 2: extending from WebSocketAdapter
Using the provided WebSocketAdapter, the management of the Connection is handled for you, and access to a simplified WebSocketBlockingConnection is also available (as well as using the NIO based write signature seen above)
package examples; import java.io.IOException; import org.eclipse.jetty.websocket.core.api.WebSocketAdapter; public class MyEchoSocket extends WebSocketAdapter { @Override public void onWebSocketText(String message) { if (isNotConnected()) { return; } try { // echo the data back getBlockingConnection().write(message); } catch (IOException e) { e.printStackTrace(); } } }
Choice 3: decorating your POJO with @WebSocket annotations.
This the easiest WebSocket you can create, and you have some flexibility in the parameters of the methods as well.
package examples; import java.io.IOException; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.websocket.core.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.core.annotations.WebSocket; import org.eclipse.jetty.websocket.core.api.WebSocketConnection; @WebSocket(maxTextSize = 64 * 1024) public class MyEchoSocket { @OnWebSocketMessage public void onText(WebSocketConnection conn, String message) { if (conn.isOpen()) { return; } try { conn.write(null,new FutureCallback(),message); } catch (IOException e) { e.printStackTrace(); } } }
The annotations you have available:
@OnWebSocketMessage: To receive websocket message events.
Examples:
@OnWebSocketMessage public void onTextMethod(String message) { // simple TEXT message received } @OnWebSocketMessage public void onTextMethod(WebSocketConnection connection, String message) { // simple TEXT message received, with Connection // that it occurred on. } @OnWebSocketMessage public void onBinaryMethod(byte data[], int offset, int length) { // simple BINARY message received } @OnWebSocketMessage public void onBinaryMethod(WebSocketConnection connection, byte data[], int offset, int length) { // simple BINARY message received, with Connection // that it occurred on. }
@OnWebSocketConnect: To receive websocket connection connected event (will only occur once).
Example:
@OnWebSocketConnect public void onConnect(WebSocketConnection connection) { // WebSocket is now connected }
@OnWebSocketClose: To receive websocket connection closed events (will only occur once).
Example:
@OnWebSocketClose public void onClose(int statusCode, String reason) { // WebSocket is now disconnected } @OnWebSocketClose public void onClose(WebSocketConnection connection, int statusCode, String reason) { // WebSocket is now disconnected }
@OnWebSocketFrame: To receive websocket framing events (read only access to the raw Frame details).
Example:
@OnWebSocketFrame public void onFrame(Frame frame) { // WebSocket frame received } @OnWebSocketFrame public void onFrame(WebSocketConnection connection, Frame frame) { // WebSocket frame received }
One More Thing … The Future
We aren’t done with our changes to Jetty 9.x and the WebSocket API, we are actively working on the following features as well…
- Mux Extension
The multiplex extension being drafted will allow for multiple virtual WebSocket connections over a single physical TCP/IP connection. This extension will allow browsers to better utilize their connection limits/counts, and allow web proxy intermediaries to bundle multiple websocket connections to a server together over a single physical connection.
- Streaming APIs
There has been some expressed interest in providing read and write of text or binary messages using the standard Java IO Writer/Reader (for TEXT messages) and OutputStream/InputStream (for BINARY messages) APIs.
Current plans for streamed reading includes new @OnWebSocketMessage interface patterns.
// In the near future, we will have the following some Streaming
// forms also available. This is a delicate thing to
// implement and currently does not work properly, but is
// scheduled.
@OnWebSocketMessage
public void onTextMethod(Reader stream) {
// TEXT message received, and reported to your socket as a
// Reader. (can handle 1 message, regardless of size or
// number of frames)
}
@OnWebSocketMessage
public void onTextMethod(WebSocketConnection connection,
Reader stream) {
// TEXT message received, and reported to your socket as a
// Reader. (can handle 1 message, regardless of size or
// number of frames). Connection that message occurs
// on is reported as well.
}
@OnWebSocketMessage
public void onBinaryMethod(InputStream stream) {
// BINARY message received, and reported to your socket
// as a InputStream. (can handle 1 message, regardless
// of size or number of frames).
}
@OnWebSocketMessage
public void onBinaryMethod(WebSocketConnection connection,
InputStream stream) {
// BINARY message received, and reported to your socket
// as a InputStream. (can handle 1 message, regardless
// of size or number of frames). Connection that
// message occurs on is reported as well.
}
And for streaming writes, we plan to provide Writer and OutputStream implementations that simply wrap the provided WebSocketConnection.
- Android Compatible Client Library
While Android is currently not Java 7 compatible, a modified websocket-client library suitable for use with Android is on our TODO list.
- Support Java API for WebSocket API (JSR356)
We are actively tracking the work being done with this JSR group, it is coming, but is still some way off from being a complete and finished API (heck, the current EDR still doesn’t support extensions). Jetty 9.x will definitely support it, and we have tried to build our Jetty 9.x WebSocket API so that the the Java API for WebSockets can live above it.
5 Comments
Shahryar Sedghi · 16/01/2013 at 03:52
I did not see the “protocol”, it was a way to actually tell server side what to start. How does configure method know what to register with WebSocketFactory or we always register one class per servlet .
In Jetty 7 & 8 we could use one Servlet with may Listeners, are we forced here to have one Servlet for one Listener type here?
Thanks
Shahryar Sedghi
jerry wu · 27/01/2013 at 05:31
in this demo , we can see the web sockets work independently of each other.
if we are writing a web chart application. if one user sends out a message, other users need to be notified with this message. how to implement it ?
thanks
alexander · 22/04/2013 at 11:03
hi,
I’m trying to use this new API with the last Jetty from maven-central (9.0.1.v20130408). I’ve used JProfiler to find memory leaks. I’ve found that after websocketadaper.close(..) was occured, someone still holds the reference to WebSocketAdaper.
Here it is a full chain:
[WebSocketAdapter]#1->org.eclipse.jetty.websocket.common.events.ListenerEventDriver#1->org.eclipse.jetty.websocket.common.WebSocketSession#3->org.eclipse.jetty.websocket.server.WebSocketServerConnection#15->org.eclipse.jetty.io.SelectChannelEndPoint#3->sun.nio.ch.SelectionKeyImpl#4->sun.nio.ch.KQueueSelectorImpl$MapEntry#15->java.util.HashMap$Entry#1710->java.util.HashMap$Entry#1976->java.util.HashMap$Entry[]#624->java.util.HashMap#216->sun.nio.ch.KQueueSelectorImpl#3->java.nio.channels.spi.AbstractSelector$1#3->java.lang.Thread#18->org.eclipse.jetty.io.SelectorManager$ManagedSelector#3->org.eclipse.jetty.io.SelectorManager$ManagedSelector[]#2->org.eclipse.jetty.server.ServerConnector$ServerConnectorManager#1->org.eclipse.jetty.server.ServerConnector#1->java.lang.Object[]#531->java.util.concurrent.CopyOnWriteArrayList#22->org.eclipse.jetty.server.Server#1->org.eclipse.jetty.server.Server$1#1->java.util.TimerTask[]#1->java.util.TaskQueue#1->java.util.TimerThread#1->java.lang.Thread[]#2->java.lang.ThreadGroup#1->java.lang.Thread#21->java.util.concurrent.locks.AbstractQueuedSynchronizer$Node#10->java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject#4->java.lang.Thread#20
What should I do additionally to remove a websocketadapter-instance from memory?
Thanks
P.s. sorry, I didn’t found the bug-tracker.
Tim Lechler · 22/04/2013 at 11:50
I have not found a way to configure Jetty websocket client to use a proxy. Will this be considered as a feature sometime in the future?
The new Jetty 9 HTTP client | Webtide Blogs · 20/11/2012 at 19:14
[…] to lead the way when it’s about the Web: years ago with Jetty Continuations, then with Jetty WebSocket, recently with Jetty SPDY and now with the first complete, ready to use, JDK 8′s Lambda […]
Comments are closed.