| name | sse-streaming |
| description | POST-based Server-Sent Events streaming for Azure Functions — HTTP streaming, chunked response parsing, reconnection |
| tier | extended |
| applyTo | **/*sse*,**/*streaming*,**/*server-sent*,**/*real-time* |
| metadata | {"version":"1.0.0","category":"web-development","tags":["sse","streaming","azure-functions","http-streaming","readablestream","real-time"],"activation":{"triggers":["SSE","streaming","server-sent events","HTTP streaming","ReadableStream","chunked response","real-time"],"context":"Working on real-time streaming from Azure Functions to client"},"prerequisites":[]} |
SSE Streaming
POST-based Server-Sent Events pattern for Azure Functions. Solves the gap where native EventSource (GET-only) can't send request bodies, and Azure Static Web Apps don't proxy WebSocket to the API layer.
Why POST-Based SSE
| Option | SWA API Support? | POST body? | Limitation |
|---|
| WebSocket | No | N/A | SWA doesn't proxy WebSocket to Functions |
| EventSource | Yes | GET only | Can't send context in request body |
| POST + ReadableStream | Yes | Yes | Recommended pattern |
Azure Functions Setup
Enable HTTP Streaming
{
"version": "2.0",
"extensions": {
"http": {
"enableHttpStream": true
}
}
}
Streaming Function Pattern
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
app.http('stream-response', {
methods: ['POST'],
authLevel: 'anonymous',
route: 'stream',
handler: async (req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
const payload = await req.json();
const stream = new ReadableStream({
async start(controller) {
try {
const aiStream = await getAIStream(payload);
for await (const chunk of aiStream) {
const sseData = `data: ${JSON.stringify({ text: chunk, done: false })}\n\n`;
controller.enqueue(new TextEncoder().(sseData));
}
controller.(
().()
);
} (error) {
controller.(
().()
);
} {
controller.();
}
},
});
{
: ,
: {
: ,
: ,
: ,
: ,
},
: stream,
};
},
});
Client Consumption
POST + ReadableStream Pattern
interface SSEMessage {
text?: string;
done: boolean;
error?: string;
}
async function streamResponse(
payload: unknown,
onChunk: (text: string) => void,
onComplete: () => void,
onError: (error: string) => void,
): Promise<void> {
const response = await fetch('/api/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
onError(`HTTP ${response.status}: ${response.statusText}`);
return;
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
{ done, value } = reader.();
(done) ;
buffer += decoder.(value, { : });
messages = buffer.();
buffer = messages.() || ;
( msg messages) {
(!msg.()) ;
json = msg.();
{
: = .(json);
(parsed.) { (parsed.); ; }
(parsed.) { (parsed.); }
(parsed.) { (); ; }
} {
}
}
}
();
}
React Hook
function useStreamedResponse() {
const [text, setText] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [error, setError] = useState<string | null>(null);
const stream = useCallback(async (payload: unknown) => {
setText('');
setIsStreaming(true);
setError(null);
await streamResponse(
payload,
(chunk) => setText(prev => prev + chunk),
() => setIsStreaming(false),
(err) => { setError(err); setIsStreaming(false); },
);
}, []);
return { text, isStreaming, error, stream };
}
Cancellation with AbortController
async function streamWithAbort(
payload: unknown,
signal: AbortSignal,
onChunk: (text: string) => void,
): Promise<void> {
const response = await fetch('/api/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal,
});
const reader = response.body!.getReader();
signal.addEventListener('abort', () => reader.cancel());
}
const controller = new AbortController();
streamWithAbort(payload, controller.signal, onChunk);
controller.abort();
SWA Timeout Handling
Azure Static Web Apps has a 45-second API timeout. For long operations, use multi-phase streaming:
app.http('long-operation', {
methods: ['POST'],
handler: async (req, context) => {
const stream = new ReadableStream({
async start(controller) {
const send = (data: object) =>
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(data)}\n\n`));
send({ phase: 'step-1', progress: 0.1 });
const result1 = await doStep1();
send({ phase: 'step-2', progress: 0.5 });
const result2 = await doStep2(result1);
send({ phase: 'complete', progress: 1.0, result: result2 });
controller.close();
},
});
return { body: stream, headers: { : } };
},
});
Error Recovery
Retry Strategy
async function streamWithRetry(
payload: unknown,
onChunk: (text: string) => void,
maxRetries: number = 2,
): Promise<void> {
let lastError: string | undefined;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
await streamResponse(payload, onChunk, () => {}, (err) => { throw new Error(err); });
return;
} catch (error) {
lastError = error instanceof Error ? error.message : 'Unknown error';
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
}
}
}
throw new Error(`Stream failed after ${maxRetries + 1} attempts: ${lastError}`);
}
Activation Patterns
| Trigger | Response |
|---|
| "SSE", "streaming", "server-sent events" | Full skill activation |
| "Azure Functions stream", "HTTP streaming" | Azure Functions Setup section |
| "ReadableStream", "chunked response" | Client Consumption section |
| "abort", "cancel stream" | Cancellation section |
| "SWA timeout", "45 second" | SWA Timeout Handling section |