> Docs > WebSocket Server

WebSocket Server

A WebSocketServer needs to be attached to an HttpServer. The WebSocketServer must be created and configured before the HttpServer starts.

HttpServer httpServer = new HttpServer( req->HttpResponse.file(200, "/tmp/ws.test.html") );
httpServer.conf().trafficDump( System.out::print );

WebSocketHandler wsHandler = new MyWsHandler();
WebSocketServer wsServer = new WebSocketServer( httpServer, wsHandler );
wsServer.conf().trafficDump( System.err::print );

httpServer.start();

See WebSocketServerConf for config options.

Test Client

You can use this simple client for testing:

/tmp/ws.test.html

<html>
    <head>
        <script type="text/javascript">
            window.onload = function()
            {
                ws = new WebSocket("ws://localhost:8080");
                ws.onmessage = function(e){ log(e.data ); };
                ws.onclose   = function(e){ log("CLOSE"); };
                ws.onerror   = function(e){ log("ERROR"); };
            };
            function log(s){ document.getElementById("log").innerHTML += s+"\n"; }
        </script>
    </head>
    <body>
        <h1>WebSocket Test</h1>

        <h2>Send:</h2>
        <input onkeydown="if(event.keyCode==13){ws.send(value);value='';}">

        <h2>Received:</h2>
        <pre id="log"></pre>
    </body>
</html>

WebSocketHandler

Each WebSocketServer contains a WebSocketHandler that handles WebSocket handshakes. The WebSocketHandler interface contains a single abstract method

    Async<WebSocketResponse> handle(WebSocketRequest request)

For each handshake request, the handler generates either a Reject or an Accept response. For example,

public class MyWsHandler implements WebSocketHandler
{
    @Override
    public Async<WebSocketResponse> handle(WebSocketRequest request)
    {
        if( isBanned(request.ip()) )
            return WebSocketResponse.reject(403, "permission denied");
        else
            return WebSocketResponse.accept( this::handleChannel );
    }

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        ...
    }
}

Note that in the default configuration, same-origin is enforced by the server before handle() is called.

If the handshake is accepted, the Accept response specifies a channelHandler. In the example above, the channel handler is this::handleChannel. A new WebSocketChannel is created after handshake and fed to the channel handler.

WebSocketChannel

A WebSocketChannel contains methods to read and write WebSocketMessage.

    Async<WebSocketMessage> readMessage();

    Async<Long>  writeMessage(WebSocketMessage message);

For example, a simple echo server:

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        return AsyncIterator
            .forEach_( channel::readMessage, channel::writeMessage )
            .finally_( channel::close );
    }

Note that if the client closes the channel gracefully, readMessage() action completes with WebSocketClose which is a subtype of End.

You may want to use the more convenient methods like readText(max) and writeText(chars)

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        AsyncIterator<String> inputs = ()->channel.readText(1000);
        return inputs
            .map( this::process )
            .forEach_( channel::writeText )
            .finally_( channel::close );
    }
    String process(String input)
    {
        switch(input)
        {
            case "time" : return Instant.now().toString();
            case "user" : return System.getProperty("user.name");
            default     : return "pardon?";
        }
    }

An example of a simple chat server:

    Async<Void> handleChannel(WebSocketChannel channel)
    {
        allChannels.put(channel, "");

        return AsyncIterator
            .forEach_( ()->channel.readText(1000), this::broadcast )
            .finally_( ()->allChannels.remove(channel) )
            .finally_( channel::close );
    }

    final ConcurrentHashMap<WebSocketChannel,String> allChannels = new ConcurrentHashMap<>();

    Async<Void> broadcast(String msg)
    {
        for(WebSocketChannel channel : allChannels.keySet())
            channel.writeText(msg); // don't wait for write completion
        return Async.VOID;
    }

WebSocketMessage

A WebSocketMessage is a subtype of ByteSource. You can read an incoming message in streaming-style:

Async<WebSocketMessage> asyncMsg = channel.readMessage();
asyncMsg.then( msg->
    AsyncIterator.forEach( msg::read, System.out::println )
);

To create an outgoing message, see static methods in WebSocketMessage. For example, to send a file

ByteSource src = new FileByteSource("/tmp/abc.txt");
WebSocketMessage msg = WebSocketMessage.text( src );
channel.writeMessage( msg );

Hot Reload

Use HotWebSocketHandler to hot-reload the server app when source code changes.

Usually, a WebSocket app works together with an HTTP app; they share the same code and the same classloader. Therefore we want the two to share the same HotReloader.

HotReloader reloader = new HotReloader().onJavaFiles(SRC_DIR);

HotHttpHandler httpHandler = new HotHttpHandler(reloader, MyHttpHandler.class);

HotWebSocketHandler wsHandler = new HotWebSocketHandler(reloader, MyWsHandler.class);

After reloading, new WebSocket channels will be handled by the new handler instance; previous channels are still being handled by the old handler instance until they are closed. A client needs to reconnect (usually by refreshing browser window) to see the effect of reloading.