Unfortunately, this blog entry is lagging behind in terms of when the release occurred. Earlier this month (1/11), Jean-Francois released Async HTTP Client 1.7.0.
Quoting his release annoucement:
This release is our first with the Grizzly 2 provider, which supports
the same set of features as Netty (and performance wise match Netty
without any problems). This release also contains our first drop of the
WebSocket support (both Netty and Grizzly).
In a previous blog entry I offered some brief examples on using the AHC with the Grizzly provider. This entry also included some maven coordinates for a bundle containing only Grizzly dependencies.
These coordinates have changed. For those interested in this bundle, use the following maven coordinates instead:
|
1 2 3 4 5 |
<dependency> <groupId>org.glassfish.grizzly</groupId> <artifactId>grizzly-http-client</artifactId> <version>1.0</version> </dependency> |
Note that we’ll only be maintaining this bundle until such time that the Sonatype Async HTTP Client project modularizes the api and providers into distinct bundles.
With that out of the way, the other new feature available is WebSockets support. Let’s review the moving parts.
The UpgradeHandler
The UpgradeHandler is a general handler/contract for any type of HTTP upgrade request. For WebSockets, there is a concrete implementation called WebSocketUpgradeHandler. Knowledge of the underlying workings of this handler aren’t really necessary. All that you need to remember is that this handler must be passed to the AsyncHttpClient.execute() method. Instances of this handler are created using its associated builder, WebSocketUpgradeHandler.Builder which defines methods that allows the developer to add/remove listeners and perform WebSocket customization:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public final static class Builder { /** * Add a {@link WebSocketListener} that will be added to the {@link WebSocket} * * @param listener a {@link WebSocketListener} * @return this */ public Builder addWebSocketListener(WebSocketListener listener) { ... } /** * Remove a {@link WebSocketListener} * * @param listener a {@link WebSocketListener} * @return this */ public Builder removeWebSocketListener(WebSocketListener listener) { ... } /** * Set the WebSocket protocol. * * @param protocol the WebSocket protocol. * @return this */ public Builder setProtocol(String protocol) { ... } /** * Set the max size of the WebSocket byte message that will be sent. * * @param maxByteSize max size of the WebSocket byte message * @return this */ public Builder setMaxByteSize(long maxByteSize) { ... } /** * Set the max size of the WebSocket text message that will be sent. * * @param maxTextSize max size of the WebSocket byte message * @return this */ public Builder setMaxTextSize(long maxTextSize) { ... } } |
The behavior properties should be self-explanatory, we’ll touch on WebSocketListeners next.
The WebSocketListener
WebSocketListener is the base interface for all WebSocketListener implementations.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public interface WebSocketListener { /** * Invoked when the {@link WebSocket} is open. * @param websocket */ void onOpen(WebSocket websocket); /** * Invoked when the {@link WebSocket} is close. * @param websocket */ void onClose(WebSocket websocket); /** * Invoked when the {@link WebSocket} is open. * @param t a {@link Throwable} */ void onError(Throwable t); } |
From there, AHC separates listener functionality into text, binary, ping, and pong listeners.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public interface WebSocketTextListener extends WebSocketListener { /** * Invoked when WebSocket text message are received. * @param message a {@link String} message */ void onMessage(String message); /** * Invoked when WebSocket text fragments are received. * * @param fragment text fragment * @param last if this fragment is the last of the series. */ void onFragment(String fragment, boolean last); } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public interface WebSocketByteListener extends WebSocketListener { /** * Invoked when bytes are available. * @param message a byte array. */ void onMessage(byte[] message); /** * Invoked when bytes of a fragmented message are available. * * @param fragment byte[] fragment. * @param last if this fragment is the last in the series. */ void onFragment(byte[] fragment, boolean last); } |
|
1 2 3 4 5 6 7 8 9 |
public interface WebSocketPingListener extends WebSocketListener { /** * Invoked when a ping message is received * @param message a byte array */ void onPing(byte[] message); } |
|
1 2 3 4 5 6 7 8 9 |
public interface WebSocketPongListener extends WebSocketListener { /** * Invoked when a pong message is received * @param message a byte array */ void onPong(byte[] message); } |
For developer convenience, we’ve added DefaultWebSocketListener that implements all interfaces where all methods (except onOpen/onClosed) are no-ops allowing behavior override only as needed.
The WebSocket
The final interface of interest is WebSocket.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
public interface WebSocket { /** * Send a byte message. * @param message a byte message * @return this */ WebSocket sendMessage(byte[] message); /** * Allows streaming of multiple binary fragments. * * @param fragment binary fragment. * @param last flag indicating whether or not this is the last fragment. * * @return this. */ WebSocket stream(byte[] fragment, boolean last); /** * Allows streaming of multiple binary fragments. * * @param fragment binary fragment. * @param offset starting offset. * @param len length. * @param last flag indicating whether or not this is the last fragment. * @return */ WebSocket stream(byte[] fragment, int offset, int len, boolean last); /** * Send a text message * @param message a text message * @return this. */ WebSocket sendTextMessage(String message); /** * Allows streaming of multiple text fragments. * * @param fragment text fragment. * @param last flag indicating whether or not this is the last fragment. * @return this. */ WebSocket streamText(String fragment, boolean last); /** * Send a <code>ping</ping> with an optional payload * (limited to 125 bytes or less). * * @param payload the ping payload. * * @return this. */ WebSocket sendPing(byte[] payload); /** * Send a <code>ping</ping> with an optional payload * (limited to 125 bytes or less). * * @param payload the pong payload. * @return this. */ WebSocket sendPong(byte[] payload); /** * Add a {@link WebSocketListener} * @param l a {@link WebSocketListener} * @return this */ WebSocket addMessageListener(WebSocketListener l); /** * Returns <code>true</code> if the WebSocket is open/connected. * * @return <code>true</code> if the WebSocket is open/connected. */ boolean isOpen(); /** * Close the WebSocket. */ void close(); } |
So, a simple example putting all the parts together might look something like:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// This example will send a WS message to a server that echoes the message back AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); AsyncHttpClient c = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config)); String wsUrl = "ws://somehost/wspath"; WebSocketListener listener = new DefaultWebSocketListener() { @Override onMessage(String message) { System.out.println("Received message: " + message); } }; WebSocketUpgradeHandler handler = new WebSocketUpgradeHandler().Builder().addWebSocketListener(listener).build(); WebSocket socket = c.prepareGet(wsUrl).execute(handler).get(); socket.sendMessage("Hello!".getBytes("UTF-8")); |
Final Notes
The Grizzly provider implements the final version of the WebSockets RFC. However, at this point in time, the client will not try to fall back to older versions of the protocol if the server doesn’t support the same version as the client. This is something I hope we can resolve in the next release.