// SSE Streaming Demo - Server-Sent Events Example for v-hono // This example demonstrates all SSE streaming helper functions: // - stream() - Basic binary streaming // - stream_text() - Text streaming // - stream_sse() - Server-Sent Events streaming // // Run with: v -enable-globals run examples/sse_demo.v // Then open a browser and navigate to http://127.0.0.1:3000 // Or use curl to test the endpoints: // curl http://127.0.0.1:3000/stream // curl http://127.0.0.1:3000/stream-text // curl http://127.0.0.1:3000/sse // // Features demonstrated: // - Basic binary streaming with stream() // - Text streaming with stream_text() // - SSE event streaming with stream_sse() // - Error handling with custom error handlers // - SSE event fields (data, event, id, retry) // - Multi-line data in SSE events // - Sleep/delay between stream writes // - onAbort callback for client disconnection module main import net.http import os import hono fn main() { mut app := hono.Hono.new() // ========================================================================= // Static HTML page for testing SSE // ========================================================================= app.get('/', fn (mut c hono.Context) http.Response { return c.html(get_sse_test_page()) }) // ========================================================================= // Basic Binary Streaming Example // ========================================================================= // Basic stream - demonstrates binary data streaming // Sets Transfer-Encoding: chunked header app.get('/stream', fn (mut c hono.Context) http.Response { return hono.c_stream(mut c, fn (mut stream hono.StreamContext) ! { // Register abort callback for client disconnection stream.on_abort(fn () { println('[Stream] Client disconnected') }) // Stream binary data in chunks for i in 0 .. 5 { data := 'Chunk ${i + 1}: Hello from binary stream!\n' stream.write(data.bytes())! stream.sleep(500) // 500ms delay between chunks } stream.write('Stream complete!\n'.bytes())! }) }) // ========================================================================= // Text Streaming Example // ========================================================================= // Text stream - demonstrates text data streaming // Sets Content-Type: text/plain; charset=utf-8 // Sets Transfer-Encoding: chunked // Sets X-Content-Type-Options: nosniff app.get('/stream-text', fn (mut c hono.Context) http.Response { return hono.c_stream_text(mut c, fn (mut stream hono.StreamContext) ! { // Register abort callback stream.on_abort(fn () { println('[StreamText] Client disconnected') }) // Stream text data with writeln (adds newline automatically) stream.writeln('=== Text Streaming Demo ===')! stream.sleep(300) for i in 0 .. 5 { stream.write_string('Processing item ${i + 1}... ')! stream.sleep(200) stream.writeln('Done!')! stream.sleep(300) } stream.writeln('')! stream.writeln('All items processed successfully!')! }) }) // ========================================================================= // SSE (Server-Sent Events) Streaming Example // ========================================================================= // SSE stream - demonstrates Server-Sent Events // Sets Content-Type: text/event-stream // Sets Cache-Control: no-cache // Sets Connection: keep-alive app.get('/sse', fn (mut c hono.Context) http.Response { return hono.c_stream_sse(mut c, fn (mut stream hono.StreamContext) ! { // Register abort callback stream.on_abort(fn () { println('[SSE] Client disconnected') }) // Send initial connection event stream.write_sse(hono.SSEEvent{ data: 'Connected to SSE stream' event: 'connect' id: '0' })! // Send periodic updates for i in 1 .. 6 { stream.sleep(1000) // 1 second delay stream.write_sse(hono.SSEEvent{ data: 'Update ${i}: Current time is ${get_timestamp()}' event: 'update' id: '${i}' })! } // Send completion event stream.write_sse(hono.SSEEvent{ data: 'Stream completed' event: 'complete' id: '999' })! }) }) // ========================================================================= // SSE with Multi-line Data Example // ========================================================================= // SSE with multi-line data - demonstrates multi-line data handling app.get('/sse-multiline', fn (mut c hono.Context) http.Response { return hono.c_stream_sse(mut c, fn (mut stream hono.StreamContext) ! { // Send event with multi-line data stream.write_sse(hono.SSEEvent{ data: 'Line 1: First line of data\nLine 2: Second line of data\nLine 3: Third line of data' event: 'multiline' id: '1' })! stream.sleep(1000) // Send JSON data (multi-line formatted) json_data := '{\n "name": "v-hono",\n "version": "1.0.0",\n "features": ["SSE", "WebSocket", "Streaming"]\n}' stream.write_sse(hono.SSEEvent{ data: json_data event: 'json' id: '2' })! }) }) // ========================================================================= // SSE with Retry Field Example // ========================================================================= // SSE with retry - demonstrates retry field for reconnection app.get('/sse-retry', fn (mut c hono.Context) http.Response { return hono.c_stream_sse(mut c, fn (mut stream hono.StreamContext) ! { // Send event with retry field (tells client to reconnect after 3 seconds) stream.write_sse(hono.SSEEvent{ data: 'This event includes a retry field' event: 'message' id: '1' retry: 3000 // 3 seconds })! stream.sleep(1000) // Send another event without retry stream.write_sse(hono.SSEEvent{ data: 'This event does not include retry' event: 'message' id: '2' })! }) }) // ========================================================================= // SSE with Error Handling Example // ========================================================================= // SSE with error handling - demonstrates custom error handler app.get('/sse-error', fn (mut c hono.Context) http.Response { return hono.c_stream_sse(mut c, fn (mut stream hono.StreamContext) ! { // Send initial event stream.write_sse(hono.SSEEvent{ data: 'Starting stream with potential error...' event: 'start' id: '1' })! stream.sleep(1000) // Simulate an error condition // In real applications, this could be a database error, API failure, etc. should_error := true if should_error { return error('Simulated error for demonstration') } // This won't be reached due to the error above stream.write_sse(hono.SSEEvent{ data: 'This message will not be sent' event: 'message' id: '2' })! }, fn (err IError, mut stream hono.StreamContext) { // Custom error handler - send error event to client println('[SSE Error Handler] Error occurred: ${err.msg()}') // Try to send error event to client before closing stream.write_sse(hono.SSEEvent{ data: 'Error: ${err.msg()}' event: 'error' id: 'error' }) or { println('[SSE Error Handler] Failed to send error event: ${err}') } }) }) // ========================================================================= // Real-time Counter Example // ========================================================================= // Real-time counter - demonstrates continuous SSE updates app.get('/sse-counter', fn (mut c hono.Context) http.Response { return hono.c_stream_sse(mut c, fn (mut stream hono.StreamContext) ! { mut counter := 0 // Send counter updates every 500ms for 20 iterations for _ in 0 .. 20 { if !stream.is_open() { println('[Counter] Stream closed, stopping') break } counter++ stream.write_sse(hono.SSEEvent{ data: '${counter}' event: 'counter' id: '${counter}' })! stream.sleep(500) } // Send completion event stream.write_sse(hono.SSEEvent{ data: 'Counter finished at ${counter}' event: 'complete' id: 'done' })! }) }) // ========================================================================= // Pipe Example - Stream file content // ========================================================================= // Pipe example - demonstrates piping data to stream app.get('/stream-pipe', fn (mut c hono.Context) http.Response { return hono.c_stream(mut c, fn (mut stream hono.StreamContext) ! { // Create some sample data to pipe sample_data := 'This is sample data that will be piped to the stream.\n'.repeat(5) // Pipe the data in chunks chunk_size := 50 mut offset := 0 for offset < sample_data.len { end := if offset + chunk_size > sample_data.len { sample_data.len } else { offset + chunk_size } chunk := sample_data[offset..end] stream.pipe(chunk.bytes())! offset = end stream.sleep(100) } }) }) // ========================================================================= // REST API endpoints for demonstration // ========================================================================= app.get('/api/status', fn (mut c hono.Context) http.Response { return c.json('{"status":"running","streaming_endpoints":["/stream","/stream-text","/sse","/sse-multiline","/sse-retry","/sse-error","/sse-counter","/stream-pipe"]}') }) // ========================================================================= // Start the server // ========================================================================= println('============================================================') println('SSE Streaming Demo Server') println('============================================================') println('') println('Server starting on http://127.0.0.1:3000') println('') println('Streaming Endpoints:') println(' http://127.0.0.1:3000/stream - Basic binary streaming') println(' http://127.0.0.1:3000/stream-text - Text streaming') println(' http://127.0.0.1:3000/sse - SSE event streaming') println(' http://127.0.0.1:3000/sse-multiline - SSE with multi-line data') println(' http://127.0.0.1:3000/sse-retry - SSE with retry field') println(' http://127.0.0.1:3000/sse-error - SSE with error handling') println(' http://127.0.0.1:3000/sse-counter - Real-time counter') println(' http://127.0.0.1:3000/stream-pipe - Pipe data to stream') println('') println('HTTP Endpoints:') println(' http://127.0.0.1:3000/ - Test page') println(' http://127.0.0.1:3000/api/status - Server status') println('') println('Test with curl:') println(' curl http://127.0.0.1:3000/stream') println(' curl http://127.0.0.1:3000/stream-text') println(' curl http://127.0.0.1:3000/sse') println('') println('============================================================') app.listen(':3000') } // get_timestamp - Get current timestamp string fn get_timestamp() string { return '${os.execute("echo %TIME%").output.trim_space()}' } // get_sse_test_page - Returns HTML test page for SSE streaming fn get_sse_test_page() string { sq := "'" return '
Test Server-Sent Events and streaming responses