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
| Event | Description | Callback Parameter |
|---|---|---|
open | Triggered when connection is established | message: Connection info |
message | Triggered when message is received | message: Received message content |
close | Triggered when connection is closed | message: Close info |
error | Triggered when error occurs | error: 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
errorevents 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
handleUpgradeto prevent unauthorized access - Resource Cleanup: Clean up related resources (timers, listeners) promptly when connection closes
- Protocol Upgrade: Clients must use
ws://orwss://protocol (recommendwss://for production) - Message Size: Avoid sending overly large messages that may cause transmission failures or performance issues