问题
Maybe this is a problem in Flask, there is no way to handle disconnection event on the server side.
In Response class, there is a method named "call_on_close", where we can add a function without argument, e.g. on_close(), it will be fired when the response object's close method called, but this doesn't happen when I call EventSource.close() from the client side in Javascript.
code on server side:
from flask import Response
r = Response(stream(), ...)
r.call_on_close(on_close)
return r
def on_close():
print "response is closed!"
def stream():
... # subscribe to redis
for message in pubsub.listen():
....
yield 'data: %s\n\n' % message
on client side: add unload handler to page with SSE
$(window).unload(
function() {
sse.close();
}
}
Anything is wrong?
Any suggestions or solution with code is appreciated!
Thanks in advance!
回答1:
The generator receives a GeneratorExit
exception, and that's when you know it will exit. For example:
def stream():
try:
i = 0
while True:
yield 'Hello {}!'.format(i)
i += 1
time.sleep(1)
finally:
# Called after a GeneratorExit, cleanup here
i = 0
@app.route('/messages')
def messages():
return Response(stream(), content_type='text/event-stream')
Will yield a infinite stream of "Hello!"
, and you will know when it's done, where you can run cleanup code. If your generator blocks the thread, it will need to be unblocked in some way (maybe pushing a dummy item) so that the generator can be closed.
回答2:
I've had a similar problem with Rails Live Controllers. The problem is that the framework doesn't seem to detect that the connection is closed until it attempts to send an Event to the client.
One approach is to send periodic "heartbeat" events to the client. I'm presently using this successfully on my Rails project with an interval of 60 seconds. I have a separate thread that "emits" these heartbeats into Redis, that my controller has subscribed to.
An alternative to the threaded approach, is to wrap the Redis pubsub block with a timeout (again, say 60 seconds). And then send the heartbeat event to the client - followed by another pubsub call. The downside to this approach, is you may miss an event while you're not subscribed.
There's more on the threading approach here: Redis + ActionController::Live threads not dying
回答3:
To extend on @Lonami's answer, a yield
is required when working with a nonblocking function that returns data
def stream():
try:
pubsub = red.pubsub()
pubsub.subscribe('chat')
#for message in pubsub.listen():#This doesn't work because it's blocking
while True:
message = pubsub.get_message()
if not message:
# The yield is necessary for this to work!
# In my case I always send JSON encoded data
# An empty response might work, too.
yield "data: {}\n\n"
sleep(0.1)
continue
# If the nonblocking get_message() returned something, proceed normally
yield 'data: %s\n\n' % message["data"]
finally:
print("CLOSED!")
# Your closing logic here (e.g. marking the user as offline in your database)
@app.route('/messages')
def messages():
return Response(stream(), content_type='text/event-stream')
来源:https://stackoverflow.com/questions/18383008/python-flask-how-to-detect-a-sse-client-disconnect-from-front-end-javascript