WebSockets
A request and its response travel over a connection between the client and the server. On the web, this is a TCP connection.
Either side can send data on a connection at any time. HTTP adds a rule: the client sends a request, and the server sends one response to it.
A browser usually keeps its connection to a server open after a response and sends the next request on it, because opening a new connection takes time. It closes the connection only after a while with no requests. Marcus’s client always has a long poll waiting, so its connection stays open. So the server could send something to Marcus’s client on that connection at any time. HTTP’s rule is what stops it.
A WebSocket keeps the connection open and drops HTTP’s rule. Once a WebSocket is set up, the client and the server each send messages on it whenever they want. WebSockets were standardized in RFC 6455 in December 2011, and all major browsers have supported them since about 2012 to 2013.
Opening a WebSocket
Marcus’s client opens a WebSocket at /courses/7/live. Like /courses/7/updates, it covers all of Marcus’s rooms in course 7, so it replaces the long poll. The WebSocket starts as an HTTP request:
GET /courses/7/live HTTP/1.1
Host: courselore.example
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
The Upgrade and Connection headers ask the server to stop using HTTP on this connection and to switch to the WebSocket protocol. Sec-WebSocket-Version names the version of the protocol the client uses. If the server agrees, it responds:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
The client puts a random value in Sec-WebSocket-Key, and the server answers with a value computed from it in Sec-WebSocket-Accept. This shows the client that the server understood the request as a WebSocket request.
101 Switching Protocols is the last HTTP response on this connection. After it, the connection carries WebSocket messages in both directions. This request and its response are called the opening handshake. It is an ordinary HTTP request, so it passes through the same proxies and load balancers as any other request to Courselore.
The handshake carries Marcus’s credentials, as every request to Courselore does. The server authenticates the handshake and learns that this WebSocket belongs to Marcus. It does this once, when the WebSocket opens.
Messages on the connection
Each message is sent in a frame: a header of 2 to 14 bytes, followed by the data. Compare that header with the 700 to 800 bytes of headers on a typical HTTP request. Our messages are small JSON objects, and each has a type.
The server sends the client two types of messages. When Priya posts another message, the server sends Marcus’s client:
{
"type": "message.created",
"message": {
"id": 61851,
"room_id": 12,
// other message attributes
}
}
When Priya types in Team 4’s room, the server sends Marcus’s client:
{
"type": "typing",
"room_id": 12,
"user": {
"id": 7,
"name": "Priya Shah"
}
}
The client sends the server one type of message. While Priya types, Priya’s client sends:
{
"type": "typing",
"room_id": 12
}
The typing event from Priya’s client names only the room. The server knows from the handshake that this WebSocket belongs to Priya, so it adds Priya’s name before it passes the event on. So each typing event costs one frame.
In the browser
Here is the client’s code:
const socket = new WebSocket("wss://courselore.example/courses/7/live");
socket.onmessage = (event) => {
const update = JSON.parse(event.data);
if (update.type === "message.created") showMessage(update.message);
if (update.type === "typing") showTyping(update.room_id, update.user);
};
input.oninput = () => socket.send(JSON.stringify({ type: "typing", room_id: openRoomId }));
new WebSocket(...) sends the opening handshake. The browser calls socket.onmessage each time a message arrives from the server, and event.data holds the message’s text. The client reads the type and either shows the new message or shows who is typing. socket.send sends a message to the server. Here, the client sends a typing event whenever Priya types in the input box.
This code is simplified. showMessage and showTyping stand for the code that updates the page, and openRoomId is the room on the screen. A real client also sends a typing event at most once every 3 seconds, not on every key press.
The URL starts with wss://, which means a WebSocket over an encrypted connection. The two work like https:// and http://.