24

也许这是 Flask 中的一个问题,没有办法在服务器端处理断开连接事件。

在Response 类中,有一个名为“call_on_close”的方法,我们可以在其中添加一个不带参数的函数,例如on_close(),它会在调用响应对象的close 方法时触发,但在我调用EventSource 时不会发生这种情况。在 Javascript 中从客户端关闭()。

服务器端代码:

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

在客户端:将卸载处理程序添加到带有 SSE 的页面

$(window).unload(
  function() {
    sse.close();
  }
}

有什么不对吗?

任何带有代码的建议或解决方案表示赞赏!

提前致谢!

4

3 回答 3

8

生成器收到一个GeneratorExit异常,那是你知道它会退出的时候。例如:

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')

将产生无限的 流"Hello!",并且您将知道它何时完成,您可以在哪里运行清理代码。如果您的生成器阻塞了线程,则需要以某种方式解除阻塞(可能是推送一个虚拟项目),以便可以关闭生成器。

于 2019-05-17T14:13:16.097 回答
2

我对 Rails Live Controllers 也有类似的问题。问题是框​​架似乎没有检测到连接已关闭,直到它尝试向客户端发送事件。

一种方法是向客户端发送周期性的“心跳”事件。我目前在我的 Rails 项目中以 60 秒的间隔成功使用它。我有一个单独的线程将这些心跳“发送”到 Redis 中,我的控制器已经订阅了该线程。

线程化方法的替代方法是使用超时(再次,例如 60 秒)包装 Redis pubsub 块。然后将心跳事件发送到客户端 - 然后是另一个 pubsub 调用。这种方法的缺点是,您可能会在未订阅时错过某个活动。

这里有更多关于线程方法的信息: Redis + ActionController::Live threads not dead

于 2013-11-01T20:48:54.630 回答
2

要扩展@Lonami 的答案,使用yield返回数据的非阻塞函数时需要 a

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')
于 2020-02-09T07:10:47.000 回答