27.1. WebSockets
Table of Contents
Why WebSockets Exist
HTTP is request-response and usually one-way. The client sends a request, the server replies, and the connection is done or kept alive only for more requests. If you want live updates, you often end up polling:
- Client: “Any new messages?” every 3 seconds
- Server: “Nope” or “Here is something”
This wastes resources and does not feel instant.
WebSockets solve this by creating a persistent, full-duplex connection between client and server. After the initial HTTP handshake, both sides can send messages to each other at any time until the connection is closed.
Typical use cases:
- Chat applications
- Live dashboards and stock tickers
- Multiplayer games
- Collaborative editing (Google Docs style)
- Real-time notifications
In all of these, you need low-latency, bidirectional communication and do not want to re-open a connection on every update.
Key idea: WebSockets keep one long-lived connection open, and both client and server can send messages at any time, without waiting for a request.
The WebSocket Handshake
WebSockets begin as a normal HTTP/1.1 request. The client says, “I would like to upgrade this HTTP connection to WebSocket.” If the server agrees, it responds and both sides switch to the WebSocket protocol on the same TCP connection.
A typical WebSocket handshake request from a browser looks like:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: https://example.comImportant headers:
Upgrade: websocket
Asks to switch protocols from HTTP to WebSocket.Connection: Upgrade
Says theUpgradeheader should be processed.Sec-WebSocket-Key
A random base64 string that the server uses to prove it accepts the protocol.
The server replies:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Status 101 means “Switching Protocols.” After this, the data on the connection is no longer HTTP. It is WebSocket frames.
Table summary:
| Step | Who | What happens |
|---|---|---|
| HTTP upgrade request | Client | Sends Upgrade: websocket headers |
| Upgrade decision | Server | Validates headers, decides to accept |
| HTTP 101 response | Server | Sends Sec-WebSocket-Accept and status |
| Protocol switch | Both | Start sending WebSocket frames |
You usually do not implement this handshake by hand. Libraries and frameworks handle it. But it is important to know that WebSockets are not a separate port or magic protocol. They are:
- HTTP/1.1 connection
- Upgrade to WebSocket
WebSocket Connections and Messages
Once the handshake is done, the WebSocket connection is:
- Persistent
It stays open until one side closes it or a network error occurs. - Full-duplex
Client and server can talk at the same time. - Framed
Data is sent as frames, which contain type and payload.
Message Types
Most libraries abstract frames away and give you messages. Common message types:
- Text messages
Typically UTF-8 strings such as JSON. - Binary messages
Raw binary data, for example images or protocol buffers. - Control frames
Used for pings, pongs, and closing the connection.
In a typical backend, you will mostly send and receive text messages encoded as JSON.
Example JSON chat message:
{
"type": "message",
"room": "general",
"sender": "alice",
"text": "Hello, everyone!",
"timestamp": "2026-08-28T12:34:56Z"
}It is common to define a message format like this so client and server both know how to interpret messages.
Important rule: Always define a clear JSON schema for your WebSocket messages, including a type field, so you can safely parse and route different message kinds.
WebSocket vs HTTP APIs
You already know how to build REST or HTTP APIs. WebSockets are different in several important ways.
Communication Pattern
| Feature | HTTP / REST | WebSocket |
|---|---|---|
| Direction | Client → Server (request), Server → Client (response only) | Both directions at any time |
| Connection life | Short-lived (per request), or reused with keep-alive | Long-lived until closed |
| Typical use | CRUD, resources, business operations | Real-time updates, events, streams |
With HTTP:
- Client calls
POST /messages - Client polls
GET /messagesfor updates
With WebSockets:
- Client sends
"new_message"via WebSocket - Server immediately sends
"message_created"events to other connected clients
State and Protocol Design
HTTP APIs are usually stateless per request. Every request carries all necessary information.
WebSockets often have a stateful session on the server side:
- Which user is connected
- Which rooms / topics they are subscribed to
- What permissions they have
This means:
- You must explicitly handle authentication and authorization when the connection is created.
- You must carefully manage memory and cleanup when connections close.
When to Use WebSockets Instead of HTTP
Use WebSockets when:
- You need fast, real-time updates from server to client
- You need continuous streams of data
- You need interactive, bidirectional communication
Use only HTTP APIs when:
- You mostly do classic CRUD operations
- Slight delays are acceptable
- You do not want the complexity of managing long-lived connections
It is common to combine both: REST for normal operations and WebSockets for live updates.
Basic WebSocket Flow with an Example
Consider a simple chat room feature.
Step 1: Client Connects
Client uses JavaScript in the browser:
const socket = new WebSocket("wss://api.example.com/ws/chat");
socket.onopen = () => {
console.log("Connected to chat");
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("New message:", data);
};
socket.onclose = () => {
console.log("Disconnected from chat");
};
Note the wss:// scheme:
ws://is WebSocket over plain TCPwss://is WebSocket over TLS, similar to HTTPS vs HTTP
Step 2: Client Sends a Message
socket.send(JSON.stringify({
type: "message",
room: "general",
text: "Hello from the browser!"
}));On the backend (pseudo Python code):
async def chat_websocket(websocket):
await websocket.accept()
try:
while True:
raw = await websocket.receive_text()
data = json.loads(raw)
if data["type"] == "message":
print("Message received:", data["text"])
# Here you might broadcast to others
except WebSocketDisconnect:
print("Client disconnected")Important things you can see:
- The server accepts the connection once the handshake is done.
- After that, there is a loop that receives messages until the client disconnects or an error occurs.
- Messages are JSON strings, so server and client must agree on the format.
Step 3: Server Sends Messages
The server can send messages at any time:
await websocket.send_text(json.dumps({
"type": "system",
"text": "Welcome to the chat!"
}))
The client will receive this in its onmessage handler.
Handling Disconnections and Errors
WebSockets are long-lived, but they are not permanent. Clients close browser tabs, networks drop, servers restart.
You need to:
- Detect disconnects
- Clean up server-side state
- Optionally attempt reconnection on the client
Common close reasons:
- Client calls
socket.close() - Server decides to close the connection
- Network errors or timeouts
Example of a client with simple reconnection logic:
function connect() {
const socket = new WebSocket("wss://api.example.com/ws/chat");
socket.onopen = () => {
console.log("Connected");
};
socket.onclose = (event) => {
console.log("Disconnected, trying to reconnect in 3s...");
setTimeout(connect, 3000);
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
socket.close();
};
}
connect();
On the server, you often wrap your receive loop in a try / except and remove the client from any subscription lists when a WebSocketDisconnect or equivalent is raised.
Always clean up server-side state when a WebSocket closes, or you risk memory leaks and stale subscriptions.
Broadcasting and Multiple Clients
In many real-time applications, you have many clients listening to the same events.
For example, in a chat room:
- User A sends a message
- Server receives it
- Server sends the new message to all clients in that room
A very simplified server-side structure might be:
rooms = {
"general": set(), # each item is a websocket connection
"random": set(),
}
async def chat_websocket(websocket, room_name):
await websocket.accept()
rooms[room_name].add(websocket)
try:
while True:
raw = await websocket.receive_text()
data = json.loads(raw)
if data["type"] == "message":
# Broadcast to all clients in the room
for client in rooms[room_name]:
await client.send_text(raw)
except WebSocketDisconnect:
rooms[room_name].remove(websocket)This example:
- Keeps track of connections per room using sets
- Sends each incoming message to every client in that room
- Removes connections on disconnect
In production systems you usually:
- Store connection lists in a more scalable structure
- Use a message broker like Redis pub/sub to broadcast across multiple server instances
- Limit which messages can be broadcast from which users (authorization)
Message Design and Versioning
Because WebSockets often carry free-form JSON, it is very easy to create a mess where no one knows what messages mean.
Good practices for message design:
- Always include a
typefield
Example types:"message","typing","join","leave","ping". - Define a contract
Document what each type expects and returns. - Allow for versioning
You might include aversionfield or design messages so old clients can ignore unknown fields safely.
Example, slightly more robust JSON message:
{
"type": "message",
"version": 1,
"payload": {
"room": "general",
"text": "Hello",
"sender": "alice"
}
}On the server:
data = json.loads(raw)
msg_type = data.get("type")
version = data.get("version", 1)
payload = data.get("payload", {})
if msg_type == "message" and version == 1:
text = payload["text"]
# handle messageTreat WebSocket messages like API contracts. Validate them and handle unknown or invalid messages gracefully, instead of assuming they are correct.
Scaling Considerations
Even though this course has separate chapters for performance and scalability, a few WebSocket-specific ideas are useful here.
Many Connections
Each WebSocket connection usually maps to:
- A TCP connection
- Some memory on the server
- Possibly a task or thread waiting for data
To support many connections:
- Use an asynchronous server that can handle multiple sockets concurrently
- Tune OS limits, like max open file descriptors
- Consider horizontal scaling with multiple server instances
Multiple Server Instances
If you run multiple backend instances behind a load balancer, a message received on instance A might need to be delivered to a client connected to instance B.
To handle this:
- Use a shared message bus, such as Redis pub/sub
- When a message arrives, publish it to a channel
- All instances subscribed to that channel broadcast to their local connections
Example conceptual flow:
- Client 1 (connected to server A) sends chat message.
- Server A publishes the message to
chat.generalin Redis. - Servers A, B, C are subscribed to
chat.general. Each receives the message. - Each server sends the message only to its local clients in that room.
You do not need to implement this now, but it is important to see that WebSockets interact strongly with architecture and scaling decisions.
Security Basics for WebSockets
Full backend security is covered elsewhere, but a few focused points for WebSockets:
- Authenticate the connection
Use a token or session when the WebSocket is created. You can pass a token in: - A query parameter (for example
wss://example.com/ws/chat?token=...) - A header, if your client supports custom headers
- The first message after the connection opens
- Authorize actions
Check permissions before letting a user: - Join a room
- Subscribe to certain event types
- Perform administrative operations
- Validate input
Treat incoming WebSocket messages like HTTP requests: - Parse JSON
- Validate fields and types
- Enforce limits on sizes and rates
- Use WSS in production
Always usewss://so data is encrypted on the wire.
Never trust data coming from a WebSocket client. Always authenticate, authorize, and validate, just like for normal HTTP APIs.
Summary
WebSockets give your backend the ability to:
- Keep long-lived connections
- Send and receive messages at any time
- Build real-time features without constant polling
You learned:
- How the WebSocket handshake upgrades from HTTP to a persistent connection
- How messages flow in both directions
- How WebSockets differ from normal HTTP APIs
- How to handle disconnects, broadcasting, and message design
- Basic scalability and security considerations for WebSockets
In the larger picture of backend development, WebSockets are a specialized tool. You will still use HTTP and REST for most operations, but WebSockets become essential when your application needs real-time, interactive communication.
Views: 10
KAHIBARO