KAHIBARO
Discord Login Register

27.1. WebSockets

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:

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:

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:

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

Important headers:

The server replies:

http
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:

StepWhoWhat happens
HTTP upgrade requestClientSends Upgrade: websocket headers
Upgrade decisionServerValidates headers, decides to accept
HTTP 101 responseServerSends Sec-WebSocket-Accept and status
Protocol switchBothStart 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:

  1. HTTP/1.1 connection
  2. Upgrade to WebSocket

WebSocket Connections and Messages

Once the handshake is done, the WebSocket connection is:

Message Types

Most libraries abstract frames away and give you messages. Common message types:

In a typical backend, you will mostly send and receive text messages encoded as JSON.

Example JSON chat message:

json
{
  "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

FeatureHTTP / RESTWebSocket
DirectionClient → Server (request), Server → Client (response only)Both directions at any time
Connection lifeShort-lived (per request), or reused with keep-aliveLong-lived until closed
Typical useCRUD, resources, business operationsReal-time updates, events, streams

With HTTP:

With WebSockets:

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:

This means:

When to Use WebSockets Instead of HTTP

Use WebSockets when:

Use only HTTP APIs when:

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:

js
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:

Step 2: Client Sends a Message

js
socket.send(JSON.stringify({
  type: "message",
  room: "general",
  text: "Hello from the browser!"
}));

On the backend (pseudo Python code):

python
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:

Step 3: Server Sends Messages

The server can send messages at any time:

python
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:

Common close reasons:

Example of a client with simple reconnection logic:

js
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:

A very simplified server-side structure might be:

python
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:

In production systems you usually:

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:

  1. Always include a type field
    Example types: "message", "typing", "join", "leave", "ping".
  2. Define a contract
    Document what each type expects and returns.
  3. Allow for versioning
    You might include a version field or design messages so old clients can ignore unknown fields safely.

Example, slightly more robust JSON message:

json
{
  "type": "message",
  "version": 1,
  "payload": {
    "room": "general",
    "text": "Hello",
    "sender": "alice"
  }
}

On the server:

python
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 message

Treat 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:

To support many connections:

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:

Example conceptual flow:

  1. Client 1 (connected to server A) sends chat message.
  2. Server A publishes the message to chat.general in Redis.
  3. Servers A, B, C are subscribed to chat.general. Each receives the message.
  4. 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:

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:

You learned:

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

Comments

Please login to add a comment.

Don't have an account? Register now!