Skip to content

Websocket Subprotocol

What is Websocket Subprotocol

You may take a look at the RFC or MDN to get a quick understanding what it is.

In general, websocket is a bi-directional, two way communication protocol. It has the server side and client side. The message being transferred could be in text, binary format.

In practice, we will define our own protocol on top of websocket. Take an example if we want to define a chat protocol. The message transferred in the websocket could be something like:

txt
{
    "messageType" : "userJoin" 
    "payload" : {
        "userId": "1234"
    }
}

{
    "messageType" : "userLeave" 
    "payload" : {
        "userId": "1234"
    }
}

{
    "messageType" : "userChat" 
    "payload" : {
        "from": "1234",
        "to": "2345",
        "message": "hello"
    }
}

...

Or if we want to make it high performance, we can even define binary protocol, e.g.

txt

|messageType | payload |
|------------|---------|
| 4 byte     | n bytes.|

messageType can be

    * userJoin, value=1 
    * userLeft, value=2
    * userChat, value=3

for example: 

// userJoin

 1            1234
|------------|---------|
| messageType| userId  |
| 4 byte     | 4 bytes.|

// userLeft

 2            1234
|------------|---------|
| messageType| userId  |
| 4 byte     | 4 bytes.|

// chat

 2            1234        2345        hello
|------------|-----------|-----------|-----------|
| messageType| fromUserId| toUserId  | message   |
| 4 byte     | 4 bytes   | 4 bytes   | n bytes   |

Say if our server support both text and binary protocol, our client can then do a negotiation to select one of them.

In theory, client can add custom header in the http websocket upgrade request, and server respond back the negotiated protocol in custom header. But Websocket not allow adding extra header in response.

However, it defined something call subprotocol, a dedicated header for protocol negotiation Sec-WebSocket-Protocol.

Message Flow

As the example below

  • client send preferred protocol: Sec-WebSocket-Protocol: chat_text_v1, chat_binary_v1
  • server response protocol it can support: Sec-WebSocket-Protocol: chat_text_v1
txt

// client sending the websocket upgrade request

GET /chat HTTP/1.1
Host: chat.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Protocol: chat_text_v1, chat_binary_v1
...

// server response websocket upgrade and also the subprotocol

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Protocol: chat_text_v1
...

There are something need to pay attention:

  1. server can only respond protocol in client's preferred list
  2. adding version in your protocol could make the upgrade easier in the future. (Also possible to make things non-breaking during upgrade)

Example

Here is an example of Websocket Subprotocol implementation with mu-server and jdk-http-client

java
@Test
void testWebsocketSubProtocol() throws InterruptedException {

    CountDownLatch latch = new CountDownLatch(1);

    MuServer server = MuServerBuilder.httpsServer()
        .addHandler(
            WebSocketHandlerBuilder.webSocketHandler()
                .withPath("/echo-socket")
                .withWebSocketFactory((request, responseHeaders) -> {

                    // 2. server received preferred sub protocols from client requests
                    String clientRequestProtocol = request.headers()
                        .get("Sec-WebSocket-Protocol");

                    String[] protocols;
                    if (clientRequestProtocol != null &&
                        (protocols = clientRequestProtocol.split(",")).length > 0) {
                        // 3. server respond back sub protocol it can support
                        responseHeaders.set("Sec-WebSocket-Protocol", protocols[0].trim());
                    }

                    return new BaseWebSocket() {
                        public void onClientClosed(int statusCode, String reason) throws Exception {
                            super.onClientClosed(statusCode, reason);
                            latch.countDown();
                        }
                    };
                })
        )
        .start();

    final String[] subProtocolFromServer = new String[1];
    WebSocket.Listener listener = new WebSocket.Listener() {
        @Override
        public void onOpen(WebSocket webSocket) {
            // 4 client receive the supported sub protocol from server
            subProtocolFromServer[0] = webSocket.getSubprotocol();
            webSocket.sendClose(1000, "normal close");
        }
    };

    URI uri = URI.create(server.uri().toString().replace("http", "ws"))
            .resolve("/echo-socket");
    client.newWebSocketBuilder()
            // 1. client sending preferred sub protocols
            .subprotocols("chat_text_v1", "chat_binary_v1")
            .connectTimeout(Duration.ofMillis(5000))
            .buildAsync(uri, listener);

    latch.await(1, TimeUnit.MINUTES);
    assertThat(subProtocolFromServer[0]).isEqualTo("chat_text_v1");
}

Summary

We simply covered:

  • What is websocket subprotocol
  • What the message flow looks like
  • An example of using websocket subprotocol

Hope this helps and feel free to take a look my other articles.

Reference