Using SSE
This document describes how to implement SSE (Server-Sent Events) server push in HTTP Cloud Functions to support unidirectional real-time data streaming from server to clients.
What is SSE
「SSE (Server-Sent Events)」is a technology for servers to push real-time data to clients, based on the HTTP protocol. Unlike WebSocket, SSE is unidirectional communication - it only supports server-to-client data push, while clients send data through regular HTTP requests.
Key Features:
- Unidirectional Push: Server actively pushes data to clients; clients can only receive
- HTTP-based: Uses standard HTTP protocol with good compatibility and easy implementation
- Auto Reconnection: Clients automatically reconnect after disconnection
- Text Format: Transmitted data is in text format (usually JSON)
- Lightweight: Simpler implementation and lower resource usage compared to WebSocket
Typical Use Cases:
- AI conversation streaming output
- Real-time log push
- Progress update notifications
- Server status monitoring
- Real-time data dashboards
Basic Usage
1. Creating SSE Cloud Function
Get SSE instance through context.sse() in Cloud Functions, then use the send() method to push data.
exports.main = async function (event, context) {
// Get SSE instance
const sse = context.sse?.();
if (sse && !sse.closed) {
// Send message
sse.send({
data: "Hello from SSE!"
});
// End connection
sse.end({
data: "Goodbye!"
});
}
return "";
};
2. Sending Multiple Messages
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
// Send first message
sse.send({
data: "First message"
});
// Send second message
sse.send({
data: "Second message"
});
// Send JSON data
sse.send({
data: { message: "This is JSON data", timestamp: Date.now() }
});
// End connection
sse.end({
data: "All messages sent"
});
}
return "";
};
3. Streaming Data Push
Simulate streaming scenarios like AI conversations:
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
const text = "This is a streaming text output, each character will be pushed individually to the client.";
// Push character by character
for (let i = 0; i < text.length; i++) {
if (sse.closed) break; // Check if connection is closed
sse.send({
data: text[i]
});
// Simulate delay
await new Promise(resolve => setTimeout(resolve, 100));
}
// End connection
sse.end({
data: "[Complete]"
});
}
return "";
};
SSE API
context.sse() Method
Calling context.sse() gets an SSE instance. Supports passing configuration parameters:
const sse = context.sse?.({
keepalive: false, // Keep connection alive, default true
headers: {
'X-Custom-Header': 'value',
'X-Session-ID': 'session-123',
}
});
Configuration Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
keepalive | boolean | true | Keep connection alive |
headers | Record<string, string \| string[]> | {} | Custom response headers |
ISeverSentEvent Interface
The SSE instance returned by context.sse() provides the following methods and properties:
Methods
interface ISeverSentEvent {
setEncoder(encoder: TextEncoder): void;
send(event: SseEvent | SseEvent[] | string | string[]): boolean;
end(msg?: SseEvent): void;
on(event: 'close', callback: () => void): void;
}
| Method | Description | Return Value |
|---|---|---|
send(event) | Send SSE event | boolean - Whether send succeeded |
end(msg?) | End SSE connection, optionally send last message | void |
setEncoder(encoder) | Set text encoder | void |
on('close', callback) | Listen to connection close event | void |
Properties
| Property | Type | Description |
|---|---|---|
closed | boolean | Whether connection is closed |
SseEvent Data Structure
interface SseEvent<T = string | Record<string, unknown>> {
data: T; // Data to send
comment?: string; // Comment (not processed by client)
event?: string; // Event type
id?: string; // Event ID (for reconnection)
retry?: number; // Reconnection time (milliseconds)
}
Complete Examples
1. AI Conversation Streaming Output
exports.main = async function (event, context) {
const sse = context.sse?.();
if (!sse || sse.closed) {
return { error: "Cannot establish SSE connection" };
}
// Listen to connection close
sse.on("close", () => {
console.log("Client disconnected");
});
try {
// Parse user message
const { message } = event;
// Send start marker
sse.send({
event: "start",
data: { status: "Starting reply generation" }
});
// Simulate AI generating reply (can call AI model in practice)
const reply = "This is AI-generated reply content, pushed character by character to user.";
for (let i = 0; i < reply.length; i++) {
if (sse.closed) break;
sse.send({
event: "message",
data: { content: reply[i], index: i }
});
await new Promise(resolve => setTimeout(resolve, 50));
}
// Send completion marker
sse.end({
event: "done",
data: { status: "Generation complete", totalLength: reply.length }
});
} catch (error) {
// Send error info
sse.end({
event: "error",
data: { message: error.message }
});
}
return "";
};
2. Real-time Progress Push
exports.main = async function (event, context) {
const sse = context.sse?.();
if (!sse || sse.closed) {
return { error: "Cannot establish SSE connection" };
}
// Simulate long-running task
async function longRunningTask() {
const totalSteps = 10;
for (let step = 1; step <= totalSteps; step++) {
if (sse.closed) break;
// Send progress update
sse.send({
event: "progress",
data: {
step,
total: totalSteps,
percent: Math.round((step / totalSteps) * 100),
message: `Processing step ${step}...`
}
});
// Simulate task execution
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
await longRunningTask();
// Task complete
sse.end({
event: "complete",
data: { status: "Task complete", timestamp: Date.now() }
});
return "";
};
3. Real-time Log Push
// Log queue (can use message queue in practice)
const logQueue = [];
exports.main = async function (event, context) {
const sse = context.sse?.();
if (!sse || sse.closed) {
return { error: "Cannot establish SSE connection" };
}
// Listen to connection close
let intervalId;
sse.on("close", () => {
clearInterval(intervalId);
console.log("Stopped pushing logs");
});
// Push logs periodically
intervalId = setInterval(() => {
if (sse.closed) {
clearInterval(intervalId);
return;
}
// Get logs from queue
const logs = getLatestLogs(); // Actual implementation
if (logs.length > 0) {
// Batch send logs
sse.send(logs.map(log => ({
event: "log",
data: {
level: log.level,
message: log.message,
timestamp: log.timestamp
}
})));
}
}, 1000);
// Keep connection
return "";
};
// Simulate getting logs
function getLatestLogs() {
// In practice, can get from database or logging system
return [
{ level: "info", message: "Application started", timestamp: Date.now() },
{ level: "debug", message: "Processing request", timestamp: Date.now() }
];
}
4. Server Status Monitoring
exports.main = async function (event, context) {
const sse = context.sse?.({
headers: {
'X-Monitoring-Session': `session-${Date.now()}`
}
});
if (!sse || sse.closed) {
return { error: "Cannot establish SSE connection" };
}
// Monitoring data push
let intervalId = setInterval(() => {
if (sse.closed) {
clearInterval(intervalId);
return;
}
// Get system status
const status = {
cpu: Math.random() * 100,
memory: Math.random() * 100,
activeConnections: Math.floor(Math.random() * 1000),
timestamp: Date.now()
};
sse.send({
event: "metrics",
data: status
});
}, 2000);
// Set timeout (optional)
setTimeout(() => {
clearInterval(intervalId);
sse.end({
event: "timeout",
data: { message: "Monitoring session timeout" }
});
}, 300000); // 5 minutes timeout
return "";
};
Advanced Usage
1. Sending Raw SSE Messages
SSE protocol supports multiple fields (data, event, id, retry etc.), can send raw message strings:
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
// Send raw message (note: must end with newline)
sse.send("data: This is a raw message\n\n");
// Batch send raw messages
sse.send([
"event: custom\n",
"data: Custom event\n",
"id: 123\n",
"retry: 1000\n",
"\n"
]);
sse.end();
}
return "";
};
2. Batch Sending Multiple Events
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
// Send multiple events at once
sse.send([
{ event: "message", data: "First message" },
{ event: "message", data: "Second message" },
{ event: "message", data: "Third message" }
]);
sse.end();
}
return "";
};
3. Handling Line Breaks
Line breaks have special meaning in SSE protocol, pay attention when sending data with line breaks:
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
// Message with line breaks
sse.send({
data: "Line one\\nLine two\\nLine three"
});
// Multi-line text
const multilineText = `This is line one
This is line two
This is line three`;
sse.send({
data: multilineText
});
sse.end();
}
return "";
};
4. Custom Event Types
exports.main = async function (event, context) {
const sse = context.sse?.();
if (sse && !sse.closed) {
// Different event types
sse.send({
event: "notification",
data: { type: "info", message: "This is a notification" }
});
sse.send({
event: "alert",
data: { type: "warning", message: "This is a warning" }
});
sse.send({
event: "update",
data: { type: "success", message: "Update successful" }
});
sse.end();
}
return "";
};
Client Connection Examples
Browser JavaScript
// Create SSE connection
const eventSource = new EventSource('https://your-service.run.tcloudbase.com/sse');
// Listen to default message event
eventSource.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 data
}
};
// Listen to custom events
eventSource.addEventListener('notification', (event) => {
console.log('Received notification:', event.data);
});
eventSource.addEventListener('progress', (event) => {
const data = JSON.parse(event.data);
console.log(`Progress: ${data.percent}%`);
});
// Listen to connection open
eventSource.onopen = () => {
console.log('SSE connection established');
};
// Listen to errors
eventSource.onerror = (error) => {
console.error('SSE error:', error);
// Can choose to close connection
// eventSource.close();
};
// Close connection
function closeConnection() {
eventSource.close();
console.log('SSE connection closed');
}
React Example
import { useEffect, useState } from 'react';
function StreamingChat() {
const [messages, setMessages] = useState([]);
const [currentMessage, setCurrentMessage] = useState('');
useEffect(() => {
const eventSource = new EventSource('https://your-service.run.tcloudbase.com/sse');
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
setCurrentMessage(prev => prev + data.content);
});
eventSource.addEventListener('done', () => {
setMessages(prev => [...prev, currentMessage]);
setCurrentMessage('');
eventSource.close();
});
eventSource.onerror = (error) => {
console.error('Connection error:', error);
eventSource.close();
};
return () => {
eventSource.close();
};
}, []);
return (
<div>
{messages.map((msg, index) => (
<div key={index}>{msg}</div>
))}
{currentMessage && <div>{currentMessage}</div>}
</div>
);
}
Node.js Client
const EventSource = require('eventsource');
const eventSource = new EventSource('https://your-service.run.tcloudbase.com/sse');
eventSource.onmessage = (event) => {
console.log('Received message:', event.data);
};
eventSource.addEventListener('progress', (event) => {
const data = JSON.parse(event.data);
console.log(`Progress: ${data.percent}%`);
});
eventSource.onerror = (error) => {
console.error('Error:', error);
};
Using TypeScript
import { TcbEventFunction } from "@cloudbase/functions-typings";
interface MessageData {
content: string;
timestamp: number;
}
export const main: TcbEventFunction<void, Promise<string>> = async function (
event,
context
) {
const sse = context.sse?.({
keepalive: true,
headers: {
'X-Session-ID': 'session-123'
}
});
if (!sse || sse.closed) {
return JSON.stringify({ error: "Cannot establish SSE connection" });
}
sse.on("close", () => {
console.log("SSE connection closed");
});
// Send message
sse.send({
event: "message",
data: {
content: "Hello from SSE!",
timestamp: Date.now()
} as MessageData
});
// End connection
sse.end({
event: "complete",
data: { status: "Complete" }
});
return "";
};
Important Notes
⚠️ Note: The following points require special attention when using SSE
- Unidirectional Communication: SSE only supports server-to-client push; clients need other methods (like HTTP POST) to send data to server
- Connection Persistence: SSE connections occupy resources long-term; need reasonable timeout mechanisms and connection limits
- Browser Limits: Browsers have SSE connection limits per domain (usually 6); need connection management
- Auto Reconnection: Clients automatically reconnect; servers need to handle duplicate connections
- Text Format: SSE can only transmit text data; complex data needs JSON serialization
- Error Handling: Properly handle connection close and error cases to avoid resource leaks
- Return Value: Cloud Functions using SSE must return empty string or no content
- Content-Type: SSE response
Content-Typeistext/event-stream, automatically set by framework
SSE vs WebSocket
| Feature | SSE | WebSocket |
|---|---|---|
| Communication | Unidirectional (Server→Client) | Bidirectional (Server↔Client) |
| Protocol | HTTP | WebSocket protocol |
| Data Format | Text (usually JSON) | Text or binary |
| Auto Reconnect | Yes | No (manual implementation needed) |
| Browser Compatibility | Good (except IE) | Good |
| Implementation Complexity | Simple | Relatively complex |
| Resource Usage | Lower | Higher |
| Use Cases | Unidirectional data push | Real-time bidirectional communication |
Selection Guide:
- Only need server push: Choose SSE
- Need bidirectional real-time communication: Choose WebSocket
- Prefer simple implementation: Choose SSE
- Need binary data transmission: Choose WebSocket