🔥 0
0
Lesson 2 of 5 18 min +200 XP

Connection Lifecycle & Upgrade

WebSocket connections don't just appear - they start life as HTTP requests. Understanding this handshake process is key to debugging connection issues.

The Upgrade Handshake

WebSocket uses HTTP to establish the connection, then upgrades to the WebSocket protocol.

Step 1: Client sends HTTP upgrade request
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Step 2: Server accepts with 101
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Step 3: WebSocket connection established!
Now both sides can send frames freely

Key Headers Explained

Header Purpose
Upgrade: websocket Requests protocol upgrade to WebSocket
Connection: Upgrade Indicates connection should be upgraded
Sec-WebSocket-Key Random base64 value for security handshake
Sec-WebSocket-Accept Server's proof it understands WebSocket
Sec-WebSocket-Version Protocol version (13 is current standard)

The Security Handshake

The Sec-WebSocket-Key and Sec-WebSocket-Accept headers prevent accidental upgrades:

// How the server computes Sec-WebSocket-Accept
const crypto = require('crypto');

function computeAcceptKey(clientKey) {
  const MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  return crypto
    .createHash('sha1')
    .update(clientKey + MAGIC_STRING)
    .digest('base64');
}

// Example
// Client sends: Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
// Server responds: Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Why This Matters

This handshake prevents a misconfigured HTTP server from accidentally accepting WebSocket connections. The magic string is a fixed GUID in the WebSocket spec.

Connection States

A WebSocket connection goes through these states:

CONNECTING
readyState: 0
OPEN
readyState: 1
CLOSING
readyState: 2
CLOSED
readyState: 3
const ws = new WebSocket('wss://example.com/chat');

console.log(ws.readyState); // 0 = CONNECTING

ws.onopen = () => {
  console.log(ws.readyState); // 1 = OPEN
};

ws.onclose = () => {
  console.log(ws.readyState); // 3 = CLOSED
};

// Check before sending
if (ws.readyState === WebSocket.OPEN) {
  ws.send('Hello!');
}

Lifecycle Events

onopen

Connection established, safe to send messages

onmessage

Message received from server

onerror

Error occurred (usually followed by close)

onclose

Connection closed (graceful or error)

const ws = new WebSocket('wss://example.com/chat');

ws.onopen = (event) => {
  console.log('Connected!');
  ws.send('Hello server!');
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);
};

ws.onerror = (event) => {
  console.error('WebSocket error:', event);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
  // event.code: numeric close code
  // event.reason: string explanation
  // event.wasClean: boolean, true if closed gracefully
};

Close Codes

When a connection closes, it includes a code explaining why:

Code Name Meaning
1000 Normal Closure Connection closed cleanly
1001 Going Away Server shutting down or browser navigating away
1006 Abnormal Closure Connection lost unexpectedly (network issue)
1008 Policy Violation Message violates server policy
1011 Internal Error Server encountered unexpected error

Graceful Closing

Either side can initiate a close:

// Client-side graceful close
ws.close(1000, 'User logged out');

// Server-side (Node.js ws library)
ws.close(1000, 'Session expired');

// The close handshake:
// 1. Initiator sends close frame with code + reason
// 2. Receiver acknowledges with close frame
// 3. TCP connection closes

Reconnection Strategy

Connections will drop. Always implement reconnection:

class ReconnectingWebSocket {
  constructor(url) {
    this.url = url;
    this.reconnectDelay = 1000;
    this.maxDelay = 30000;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectDelay = 1000; // Reset delay on success
    };

    this.ws.onclose = (event) => {
      if (event.code !== 1000) {
        // Abnormal close - reconnect
        console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
        setTimeout(() => this.connect(), this.reconnectDelay);

        // Exponential backoff with max
        this.reconnectDelay = Math.min(
          this.reconnectDelay * 2,
          this.maxDelay
        );
      }
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  send(data) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      console.warn('WebSocket not open, queuing message');
      // Could queue messages here
    }
  }
}
Exponential Backoff

Start with 1s delay, double each failure (1s → 2s → 4s → 8s...), cap at a maximum (30s). This prevents hammering the server when it's down.

Heartbeats / Ping-Pong

Keep connections alive and detect dead ones:

// Server side (Node.js with 'ws' library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  ws.isAlive = true;

  ws.on('pong', () => {
    ws.isAlive = true; // Client responded
  });
});

// Ping all clients every 30 seconds
const interval = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (ws.isAlive === false) {
      console.log('Client dead, terminating');
      return ws.terminate();
    }

    ws.isAlive = false;
    ws.ping(); // Client should respond with pong
  });
}, 30000);
// Client side - respond to pings (automatic in browsers)
// But you can also send application-level heartbeats:

setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
  }
}, 30000);

Key Takeaways

  • Starts as HTTP - Upgrade request with special headers
  • 101 Switching Protocols - Server accepts the upgrade
  • Four states - CONNECTING → OPEN → CLOSING → CLOSED
  • Close codes matter - 1000 = normal, 1006 = connection lost
  • Always reconnect - Use exponential backoff

Next up: Implementation - Build a real WebSocket client and server in Node.js.

🧠 Quick Quiz

Test your understanding of this lesson.

1

How does a WebSocket connection begin?

2

What does HTTP status code 101 mean in WebSocket context?

3

What is the purpose of the Sec-WebSocket-Key header?

HTTP vs WebSocket & Use Cases