Archive

Archive for December, 2011

Writing WebSocket Clients using AsyncHttpClient

December 21, 2011 3 comments

The AsyncHttpClient version newly released 1.7.0 now supports WebSocket. Both Netty and Grizzly provider supports the latest version of the specification.

Like HTTP support with AHC, WebSoket is quite simple. Starting with AHC 1.7, a new interface called UpgradeHandler is available to the client so any kind of protocol can be used. You can event replace the actual WebSocket implementation with your in case you don’t like mine 🙂

public interface UpgradeHandler<T> {

    /**
     * If the HTTP Upgrade succeed (response's status code equals 101), 
     * the {@link AsyncHttpProvider} will invoke that
     * method
     *
     * @param t an Upgradable entity
     */
    void onSuccess(T t);

    /**
     * If the upgrade fail.
     * @param t a {@link Throwable}
     */
    void onFailure(Throwable t);

For WebSocket, I wrote a simple one called WebSocketUpgradeHandler which extends the usual AsyncHandler (always required with AHC) and UpgradeHandler. The interesting part is the WebSocketUpgradeHandler.Builder, which allow you to add listeners and WebSocket properties:

        /**
         * Add a {@link WebSocketListener} that
         * will be added to the {@link WebSocket}
         *
         * @param listener a {@link WebSocketListener}
         * @return this
         */
        public Builder addWebSocketListener(WebSocketListener listener) {
            l.add(listener);
            return this;
        }

        /**
         * Remove a {@link WebSocketListener}
         *
         * @param listener a {@link WebSocketListener}
         * @return this
         */
        public Builder removeWebSocketListener(WebSocketListener listener) {
            l.remove(listener);
            return this;
        }

        /**
         * Set the WebSocket protocol.
         *
         * @param protocol the WebSocket protocol.
         * @return this
         */
        public Builder setProtocol(String protocol) {
            this.protocol = protocol;
            return this;
        }

        /**
         * 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) {
            this.maxByteSize = maxByteSize;
            return this;
        }

        /**
         * 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) {
            this.maxTextSize = maxTextSize;
            return this;
        }

        /**
         * Build a {@link WebSocketUpgradeHandler}
         * @return a {@link WebSocketUpgradeHandler}
         */
        public WebSocketUpgradeHandler build() {
            return new WebSocketUpgradeHandler(this);
        }

You can add several type of listeners like WebSocketTextListener, WebSocketByteListener, etc. To create a WebSocket, you just need to do:

        AsyncHttpClient c = new AsyncHttpClient(); 
           // or new AsyncHttpClient(new GrizzlyAsyncHttpprovider(config))

        WebSocket websocket = c.prepareGet("ws://something:port)
                .execute(
                  new WebSocketUpgradeHandler.Builder().addWebSocketListener(
                     new WebSocketByteListener() {

                    @Override
                    public void onOpen(WebSocket websocket) {
                    }

                    @Override
                    public void onClose(WebSocket websocket) {
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onMessage(byte[] message) {
                    }

                    @Override
                    public void onFragment(byte[] fragment, boolean last) {
                    }
                }).build()).get();

The AHC Future returned is a WebSocket, and it is returned as soon as the handshake is successful. The WebSocket API is also simple:

public interface WebSocket {

    /**
     * Sen a byte message.
     * @param message a byte message
     * @return this
     */
    WebSocket sendMessage(byte[] message);

    /**
     * Send a text message
     * @param message a text message
     * @return this.
     */
    WebSocket sendTextMessage(String message);

    /**
     * Add a {@link WebSocketListener}
     * @param l a {@link WebSocketListener}
     * @return this
     */
    WebSocket addMessageListener(WebSocketListener l);

    /**
     * Close the WebSocket.
     */
    void close();
}

Conclusion, writing WebSocket is super simple!!! Here is a fully asynchronous echo client

        WebSocket websocket = c.prepareGet("ws://localhost:80")
                .execute(
                  new WebSocketUpgradeHandler.Builder()
                    .addWebSocketListener(new WebSocketTextListener() {

                    @Override
                    public void onMessage(String message) {
                        System.out.println(message);
                    }

                    @Override
                    public void onFragment(String fragment, boolean last) {
                    }

                    @Override
                    public void onOpen(WebSocket websocket) {
                        websocket
                          .sendTextMessage("ECHO")
                          .sendTextMessage("ECHO");
                    }

                    @Override
                    public void onClose(WebSocket websocket) {
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();

                    }
                }).build()).get();

For any questions you can use our Google Group, irc.freenode.net #asynchttpclient or use Twitter to reach me! You can checkout the code on Github as well!