Skip to content

WebSocket

a protocol for a two-way connection that stays open

  • Sentosa beach, singapore, rope bridge
    Photo: Arulonline / Pixabay
  • Crossing, river, rope
    Photo: It_was_a_pleasure / Pixabay

Use the concept in a decision

Put “What is WebSocket?” to work in a real decision. We can help relate the concept to the system you are building or buying. The part users never see: where the data lives, what the rules are, and how other systems are allowed to talk to it.

Explore our services Get in touch

Test the assumptions behind the term

Use the explanation and linked work to frame questions for your project. We can examine the assumptions with you before a tool is chosen. Designed and ran 6 ETL/ELT pipelines — 6 ETL pipelines across three database engines

Designed and ran 6 ETL/ELT pipelines Get in touch

What it is

WebSocket is a protocol that turns a single HTTP request into a connection both sides can write to at any moment, and that stays open until one of them closes it. The IETF published it as RFC 6455 in December 2011, and the part a browser exposes to scripts, the WebSocket object, is defined by the WHATWG WebSockets Standard. Plenty of code and documentation spells it “websocket” or “web socket”, and the plural WebSockets usually means the technology as a whole.

The connection starts as an ordinary HTTP/1.1 GET. The client adds Upgrade: websocket and Connection: Upgrade, a Sec-WebSocket-Version of 13 and a Sec-WebSocket-Key, which is a random 16-byte value encoded in base64. A server that agrees answers with status 101 Switching Protocols and a Sec-WebSocket-Accept header. That value proves the server read this very handshake rather than replaying an old answer: it is the client’s key with a fixed GUID appended, hashed with SHA-1 and encoded in base64. From that moment HTTP is finished on the connection, and both sides exchange frames instead.

A frame is small. Its header starts at two bytes, grows by two or eight more when the payload is longer than 125 bytes, and by another four on frames from the client, which carry a masking key. Each frame carries an opcode: text, which must be valid UTF-8, binary, or continuation for a message split across several frames, plus three control frames for close, ping and pong. Every frame a client sends is masked with a fresh random 32-bit key, and a server never masks. Masking is not encryption. It exists so that a hostile script cannot choose the exact bytes that cross the network and use them to poison a caching proxy somewhere along the path.

Addresses use the ws:// and wss:// schemes, with port 80 and port 443 as the defaults, and wss runs the whole exchange inside TLS. Those are the ports HTTP already uses, on purpose: the handshake looks like any other web request to the firewalls and proxies it passes through.

Why it matters

HTTP is request and answer, so the server speaks only when spoken to. Plenty of products need the opposite: a chat message, a price that just moved, a cursor in a shared document, a background job that has finished. Without a channel the server can write to, the client has to poll. It asks every few seconds, most answers only say that nothing has changed, each one carries a full set of headers, and news waits up to one interval before anyone sees it. An open WebSocket connection removes both costs. The server writes the moment something happens, and the message travels with a few bytes of framing rather than a page of headers, which keeps latency down to what the network itself adds.

The price is state. An HTTP request can land on any server behind a load balancer, while a WebSocket connection belongs to one process for as long as it lives. That process holds memory for every open connection, a deploy or restart cuts all of them at once, and every proxy and load balancer on the path has to pass the upgrade through and put up with a connection that may stay quiet for minutes. Running more than one server raises a question that plain HTTP never asks: when something happens on server A, how does it reach a client whose connection lives on server B? The usual answer is a shared publish-and-subscribe channel that every server listens to, and that is one more system to operate.

So it is a choice, not a default. When only the server has news to send, server-sent events, defined in the HTML Standard, push events over an ordinary HTTP response of type text/event-stream, and the browser reconnects by itself when the response breaks off. WebSocket earns its extra moving parts when the client has as much to say as the server, as in chat, games and collaborative editing.

What it looks like in practice

The exchange below opens a chat connection. The key is the sample value from RFC 6455 itself, and the accept value is the one the standard prints, which every correct server must send back for that key.

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

After the blank line that ends the server’s answer, nothing on the connection is HTTP any more. A browser does the whole handshake behind one constructor call and hands the rest to event listeners:

const socket = new WebSocket("wss://example.com/chat");
socket.addEventListener("open", () => socket.send("hello"));
socket.addEventListener("message", (event) => console.log(event.data));
socket.addEventListener("close", (event) => {
  console.log(event.code, event.reason, event.wasClean);
});

The close event says how the connection ended. Code 1000 is a normal closure and 1001 means a peer is going away, such as a server shutting down or a browser leaving the page. Code 1006 means the connection dropped without any close frame at all. No endpoint may send it; it exists only so the program on top can tell an abnormal end from a clean one.

The line between request-and-answer and a socket shows up clearly in one project in this site’s portfolio. When an API moved onto Huma v2, every route went onto Huma’s request and response model except exactly two: the WebSocket endpoints stayed on raw Fiber, because there the code genuinely wants the socket itself and Huma’s model does not fit. A framework built around HTTP’s own shape describes and validates requests and answers well, and a long-lived two-way connection is simply a different thing.

Where it goes wrong

A page on another site opens a connection with your visitors’ cookies. The browser runs the handshake as a request to the server’s host and sends that host’s cookies with it, wherever the script that asked for it came from. A server that authenticates by cookie and never looks at the Origin header will therefore accept a connection opened by any page on the web, signed in as whoever happens to be visiting it. RFC 6455 says a server meant only for certain sites should check Origin against the ones it expects and refuse the rest with 403 Forbidden. Do it in the handshake, before the upgrade.

The access token ends up in the logs. The browser’s WebSocket constructor takes a URL and an optional list of subprotocols, and nothing else, so a script cannot add an Authorization header. The quick way round is a token in the URL, where reverse proxies and access logs write it down. The RFC leaves authentication open and names cookies, HTTP authentication and TLS authentication as options. A cookie with the Origin check above works, and so does a short-lived, single-use ticket that the page fetches over an authenticated HTTP call and spends on the handshake.

A restart turns every client into a flood. A deploy closes every connection at once, each client reports code 1006, and if they all reconnect immediately and keep trying, the server comes back up to what the RFC likens to a denial-of-service attack. RFC 6455 asks clients to wait a random time before the first attempt, suggesting anything between 0 and 5 seconds, and to wait longer after each failure, for example with truncated binary exponential backoff. A server that closes its connections with 1001 before it stops at least tells clients the break was planned.

function reconnectDelay(attempt) {
  if (attempt === 0) return Math.random() * 5000;
  const ceiling = Math.min(60_000, 1000 * 2 ** attempt);
  return Math.random() * ceiling;
}

Connections die and nobody notices. A connection that carries no traffic gives neither side any sign that the path still exists, and a peer that vanished without a close frame looks exactly like a quiet one until the next write fails. Ping and pong exist for this: an endpoint must answer every ping with a pong, and the RFC notes that a ping can serve as a keepalive or as a check that the other side still responds. The browser API offers no way to send a ping, so the timer belongs on the server: ping each connection at a fixed interval and close the ones that miss their pong.

One message eats the server’s memory. The length field in a frame header leaves room for payloads far larger than any server has memory for, and the RFC warns that a hostile peer may send one enormous frame or an endless run of small fragments. Implementations must protect themselves, so set a maximum message size in the server library and close with code 1009, message too big, when a peer exceeds it. The same backlog builds in the other direction. In the browser, bufferedAmount reports the bytes send() has queued that are still waiting to go out on the network, and the WHATWG example sends its next update only when that number is zero. On the server, a client that reads slowly fills the outgoing buffer in the same way, so each connection needs a cap on it and a rule for what happens when the cap is reached.

Further reading

Glossary

Choose for your actual constraints

Bring the workflow, the existing interfaces and the users who need the change. We can define a testable delivery and a handover your team can maintain.

See what it costs Get in touch

Make the terminology practical

Ask how “What is WebSocket?” applies to your organisation. A concrete example from your current system is enough to start the discussion.

Explore our services
welcome@engineer.company +45 50 10 61 84 Copenhagen, Denmark
QR code for WebSocket
Take this page with youhttps://engineer.company/glossary/websocket/