"Error: Network connection lost." on SSE subrequest idling for 100 seconds between events
Cloudflare worker when deployed will throw an error: "network connection lost" when reading from a sub request stream with fetch() if therres more than 100 seconds between server sent events.
this behavior isn't documented anywhere and is quite frustrating. i was able to reproduce this to happen every time by: 1. create a simple nodejs service that sends SSE chunks back. wait 2 minutes between chunks. 2. run the nodejs service locally and use ngrok to get a url forwarded 3. make and deploy a cloudflare worker that sends a fetch request to the ngrok url, and read from the streaming response
while reading from the streaming response, and error gets thrown. here is the cloudflare worker:
export default async function fetch(request, env, ctx) { try { // Check if the path is v1/benchmark const url = new URL(request.url); if (url.pathname === '/v1/benchmark') { // Benchmark endpoint URL const benchmarkUrl = "https://.<your ngrok url>.ngrok-free.app"; console.log(`Making benchmark request to: ${benchmarkUrl}`); // Make a streaming request to the benchmark server const benchResponse = await fetch(benchmarkUrl, { method: 'GET', headers: { 'Accept': 'text/event-stream' } }); // Check if the response is valid if (!benchResponse.ok) { console.error(`Benchmark server returned status: ${benchResponse.status}`); return new Response(JSON.stringify({ error: `Benchmark server error: ${benchResponse.status}` }), { status: benchResponse.status, headers: { 'Content-Type': 'application/json' } }); } // Create a TransformStream to handle and forward the streaming response const { readable, writable } = new TransformStream(); // Start streaming the response data (async () => { const reader = benchResponse.body.getReader(); const writer = writable.getWriter(); const decoder = new TextDecoder(); try { while (true) { const { done, value } = await reader.read(); if (done) break; // Decode and log the chunk const chunk = decoder.decode(value, { stream: true }); console.log(`Benchmark data chunk: ${chunk}`); // Forward the chunk to the client await writer.write(value); } console.log("Benchmark streaming completed"); await writer.close(); } catch (error) { console.error('Error in benchmark stream:', error); console.error('Error details:', { ...error, }); await writer.abort(error); } })(); // Return the readable stream to the client return new Response(readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); } else { return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } } catch (error) { console.error('Fetch error:', error); return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); }}
export default async function fetch(request, env, ctx) { try { // Check if the path is v1/benchmark const url = new URL(request.url); if (url.pathname === '/v1/benchmark') { // Benchmark endpoint URL const benchmarkUrl = "https://.<your ngrok url>.ngrok-free.app"; console.log(`Making benchmark request to: ${benchmarkUrl}`); // Make a streaming request to the benchmark server const benchResponse = await fetch(benchmarkUrl, { method: 'GET', headers: { 'Accept': 'text/event-stream' } }); // Check if the response is valid if (!benchResponse.ok) { console.error(`Benchmark server returned status: ${benchResponse.status}`); return new Response(JSON.stringify({ error: `Benchmark server error: ${benchResponse.status}` }), { status: benchResponse.status, headers: { 'Content-Type': 'application/json' } }); } // Create a TransformStream to handle and forward the streaming response const { readable, writable } = new TransformStream(); // Start streaming the response data (async () => { const reader = benchResponse.body.getReader(); const writer = writable.getWriter(); const decoder = new TextDecoder(); try { while (true) { const { done, value } = await reader.read(); if (done) break; // Decode and log the chunk const chunk = decoder.decode(value, { stream: true }); console.log(`Benchmark data chunk: ${chunk}`); // Forward the chunk to the client await writer.write(value); } console.log("Benchmark streaming completed"); await writer.close(); } catch (error) { console.error('Error in benchmark stream:', error); console.error('Error details:', { ...error, }); await writer.abort(error); } })(); // Return the readable stream to the client return new Response(readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); } else { return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } } catch (error) { console.error('Fetch error:', error); return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }); }}
Similar Threads
Recent Announcements
Continue the conversation
Join the Discord to ask follow-up questions and connect with the community
CD
Cloudflare Developers
Welcome to the official Cloudflare Developers server. Here you can ask for help and stay updated with the latest news