Skip to main content

Using WebSocket

This document describes how to implement WebSocket long connections in HTTP Cloud Functions to support real-time bidirectional communication between the server and clients.

What is WebSocket

「WebSocket」is a protocol that provides full-duplex communication over a single TCP connection, allowing servers to actively push data to clients. Unlike traditional HTTP request-response patterns, WebSocket maintains a persistent connection after establishment, enabling real-time bidirectional data transmission.

Key Features:

  • Full-duplex Communication: Server and client can send and receive messages simultaneously
  • Persistent Connection: Maintains long connection without repeated handshakes
  • Low Latency: Real-time and efficient message delivery for real-time applications
  • Protocol Upgrade: Upgrades from HTTP to WebSocket protocol via HTTP Upgrade mechanism

Typical Use Cases:

  • Real-time chat applications
  • Online collaborative editing
  • Real-time data monitoring
  • Game servers
  • Real-time notification push

Basic Usage

1. Implementing WebSocket Cloud Function

Using WebSocket in Cloud Functions requires implementing two key parts:

1.1 Implementing handleUpgrade Method

The handleUpgrade method handles WebSocket protocol upgrade requests and returns whether to allow WebSocket connection establishment.

exports.main = async function (event, context) {
// Main logic for handling WebSocket messages
if (context.ws) {
// WebSocket connection established
context.ws.on("message", (msg) => {
console.log("Received message: ", msg);
context.ws.send(`Server reply: ${msg}`);
});
}
};

// Handle WebSocket upgrade request
exports.main.handleUpgrade = async function (context) {
return {
allowWebSocket: true, // Allow WebSocket connection
};
};

1.2 Implementing Message Handling Logic

Handle various WebSocket connection events through context.ws in the main function.

2. Complete Example

exports.main = async function (event, context) {
const { ws } = context;

if (ws) {
// Connection established event
ws.on("open", (msg) => {
console.log("WebSocket connection established", msg);
ws.send("Welcome to WebSocket service");
});

// Message received event
ws.on("message", (msg) => {
console.log("Received client message: ", msg);

// Reply to client
ws.send(`Server received message: ${msg}`);

// Send JSON data
ws.send(JSON.stringify({
type: "response",
data: msg,
timestamp: Date.now()
}));
});

// Connection closed event
ws.on("close", (msg) => {
console.log("WebSocket connection closed", msg);
});

// Error event
ws.on("error", (error) => {
console.error("WebSocket error: ", error);
});
}
};

// Handle WebSocket protocol upgrade
exports.main.handleUpgrade = async function (context) {
// Can perform authentication, permission checks, etc. here
const { httpContext } = context;
const { headers } = httpContext;

// Example: Verify token
const token = headers.authorization;
if (!token) {
return {
allowWebSocket: false, // Reject connection
statusCode: 401,
headers: {
"Content-Type": "text/plain"
},
body: "Unauthorized"
};
}

// Allow connection
return {
allowWebSocket: true
};
};

WebSocket API

context.ws Object

After WebSocket connection is established, the context.ws object provides the following methods and events:

Event Listeners

EventDescriptionCallback Parameter
openTriggered when connection is establishedmessage: Connection info
messageTriggered when message is receivedmessage: Received message content
closeTriggered when connection is closedmessage: Close info
errorTriggered when error occurserror: Error object

Sending Messages

// Send text message
ws.send("Hello, client!");

// Send JSON data
ws.send(JSON.stringify({ type: "notification", content: "Update notification" }));

handleUpgrade Return Value

The handleUpgrade method returns an object to control WebSocket connection establishment:

interface UpgradeResponse {
allowWebSocket: boolean; // Whether to allow WebSocket connection
statusCode?: number; // HTTP status code (when rejecting)
headers?: Record<string, string | string[]>; // HTTP response headers (when rejecting)
body?: string; // HTTP response body (when rejecting)
}

Advanced Usage

1. Authentication

Verify client identity in handleUpgrade:

exports.main.handleUpgrade = async function (context) {
const { httpContext, extendedContext } = context;

// Verify user identity
const userId = extendedContext?.userId;
if (!userId) {
return {
allowWebSocket: false,
statusCode: 401,
body: "Login required"
};
}

// Verify permissions
const hasPermission = await checkUserPermission(userId);
if (!hasPermission) {
return {
allowWebSocket: false,
statusCode: 403,
body: "Access denied"
};
}

return { allowWebSocket: true };
};

2. Broadcasting Messages

Send messages to all connected clients:

// Store all connected clients
const clients = new Set();

exports.main = async function (event, context) {
const { ws } = context;

if (ws) {
// Add to client list
clients.add(ws);

ws.on("message", (msg) => {
// Broadcast to all clients
clients.forEach(client => {
if (client !== ws) { // Don't send to self
client.send(msg);
}
});
});

ws.on("close", () => {
// Remove disconnected client
clients.delete(ws);
});
}
};

3. Heartbeat Detection

Implement heartbeat mechanism to keep connection alive:

exports.main = async function (event, context) {
const { ws } = context;

if (ws) {
let heartbeatInterval;

ws.on("open", () => {
// Send heartbeat every 30 seconds
heartbeatInterval = setInterval(() => {
ws.send(JSON.stringify({ type: "ping" }));
}, 30000);
});

ws.on("message", (msg) => {
try {
const data = JSON.parse(msg);

// Respond to heartbeat
if (data.type === "pong") {
console.log("Received heartbeat response");
}
} catch (e) {
// Handle non-JSON messages
}
});

ws.on("close", () => {
// Clear heartbeat timer
clearInterval(heartbeatInterval);
});
}
};

4. Room Management

Implement simple room functionality:

// Room management
const rooms = new Map();

exports.main = async function (event, context) {
const { ws } = context;

if (ws) {
let currentRoom = null;

ws.on("message", (msg) => {
try {
const data = JSON.parse(msg);

// Join room
if (data.action === "join") {
currentRoom = data.room;

if (!rooms.has(currentRoom)) {
rooms.set(currentRoom, new Set());
}
rooms.get(currentRoom).add(ws);

ws.send(JSON.stringify({
type: "system",
message: `Joined room: ${currentRoom}`
}));
}

// Send message to room
if (data.action === "message" && currentRoom) {
const roomClients = rooms.get(currentRoom);
roomClients.forEach(client => {
client.send(JSON.stringify({
type: "message",
room: currentRoom,
content: data.content
}));
});
}
} catch (e) {
console.error("Message processing error:", e);
}
});

ws.on("close", () => {
// Leave room
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom).delete(ws);

// Clean up empty rooms
if (rooms.get(currentRoom).size === 0) {
rooms.delete(currentRoom);
}
}
});
}
};

Client Connection Examples

Browser JavaScript

// Connect to WebSocket
const ws = new WebSocket('wss://your-service.run.tcloudbase.com/ws');

// Connection established
ws.onopen = () => {
console.log('WebSocket connection established');
ws.send('Hello, server!');
};

// Receive messages
ws.onmessage = (event) => {
console.log('Received message:', event.data);

try {
const data = JSON.parse(event.data);
console.log('Parsed data:', data);
} catch (e) {
// Handle non-JSON messages
}
};

// Connection closed
ws.onclose = () => {
console.log('WebSocket connection closed');
};

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

// Send message
function sendMessage(msg) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(msg);
}
}

Node.js Client

const WebSocket = require('ws');

const ws = new WebSocket('wss://your-service.run.tcloudbase.com/ws');

ws.on('open', () => {
console.log('Connection established');
ws.send('Hello, server!');
});

ws.on('message', (data) => {
console.log('Received message:', data.toString());
});

ws.on('close', () => {
console.log('Connection closed');
});

ws.on('error', (error) => {
console.error('Error:', error);
});

Using TypeScript

Write WebSocket Cloud Functions with TypeScript:

import { TcbEventFunction } from "@cloudbase/functions-typings";

// Main function
export const main: TcbEventFunction = async function (event, context) {
const { ws } = context;

if (ws) {
ws.on("open", (msg) => {
console.log("WebSocket connection established:", msg);
});

ws.on("message", (msg) => {
console.log("Received message:", msg);
ws.send(`Reply: ${msg}`);
});

ws.on("close", (msg) => {
console.log("Connection closed:", msg);
});

ws.on("error", (error) => {
console.error("Error:", error);
});
}
};

// Handle upgrade request
main.handleUpgrade = async function (context) {
return {
allowWebSocket: true,
};
};

Important Notes

⚠️ Note: The following points require special attention when using WebSocket

  • Connection Persistence: WebSocket connections occupy resources long-term; control connection count and implement timeout mechanisms
  • Error Handling: Always listen to error events to avoid uncaught exceptions causing function failures
  • Message Format: Use JSON format for structured data transmission for easier parsing and extension
  • Security Verification: Implement authentication and permission checks in handleUpgrade to prevent unauthorized access
  • Resource Cleanup: Clean up related resources (timers, listeners) promptly when connection closes
  • Protocol Upgrade: Clients must use ws:// or wss:// protocol (recommend wss:// for production)
  • Message Size: Avoid sending overly large messages that may cause transmission failures or performance issues