Python Flask, how to detect a SSE client disconnect from front end Javascript

放肆的年华 提交于 2020-08-21 05:56:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!