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:
{
"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.
|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
// 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:
- server can only respond protocol in client's preferred list
- 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
@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
- https://www.rfc-editor.org/rfc/rfc6455#page-12
- https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#subprotocols
- https://medium.com/@lancers/websocket-api-sec-websocket-protocol-subprotocol-header-support-277e34164537
- https://stackoverflow.com/questions/67436517/what-is-a-websocket-subprotocol