I have a backend (Quart) API as a event source which is handling a time consuming operation. The time-consuming task takes around 2 min of time to generate some response.
To keep alive the connection I am sending heartbeat every 10 sec. I can see all this things working and my react frontend received the events correctly.
But, the problem is after every 1 min the api req gets canceled and create another request to the same endpoint.
What I want is that, keep on sending the heartbeat to the client till the time consuming task gets completed(after 2 min).
Here is my backend logic look like in python (quart):
import asyncio
from quart import Quart, Response, websocket
app = Quart(__name__)
@app.after_request
async def add_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = 'http://localhost:3000'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
return response
async def time_consuming_task(queue):
# Simulate a time-consuming task
await asyncio.sleep(120)
# Once the task is completed, put the result in the queue
await queue.put("Task completed")
async def in_progress_events(queue):
i = 0
while True:
yield f"data: {i}\n\n"
i += 1
await asyncio.sleep(1)
@app.route('/sse')
async def sse():
# Create a queue to communicate with the time-consuming task
queue = asyncio.Queue()
# Start the time-consuming task
asyncio.create_task(time_consuming_task(queue))
async def event_stream():
while True:
async for event in in_progress_events(queue):
yield event
# Check if there's a result in the queue
if not queue.empty():
result = await queue.get()
yield f"data: {result}\n\n"
yield "event: close\ndata: Connection closed\n\n"
return
# Send a heartbeat event to keep the connection alive
yield "event: heartbeat\ndata: \n\n"
await asyncio.sleep(50) # Send a heartbeat every 50 seconds
response = Response(event_stream(), content_type='text/event-stream')
response.headers['Connection'] = 'keep-alive'
response.headers['Cache-Control'] = 'no-cache'
response.headers['Transfer-Encoding'] = 'chunked'
return response
if __name__ == '__main__':
app.run()
Also, the frontend logic:
import React, { useEffect, useState } from 'react';
function SSEComponent() {
const [data, setData] = useState('');
useEffect(() => {
const eventSource = new EventSource('http://localhost:5000/sse');
eventSource.onmessage = (event) => {
console.log("onmessage", event.data);
setData(event.data);
};
eventSource.addEventListener('close', () => {
console.log("onclose");
eventSource.close();
});
eventSource.addEventListener('heartbeat', () => {
// Handle heartbeat events
console.log('Received heartbeat event');
});
eventSource.onerror = (error) => {
console.log("error", error);
};
return () => {
eventSource.close();
};
}, []);
return (
<div>
<h2>Server-Sent Events Data:</h2>
<p>{data}</p>
</div>
);
}
export default SSEComponent;
By default Quart will timeout responses that take longer than 60 seconds, for Server Sent Event routes the timeout should be set to None
i.e. response.timeout = None
. See docs