【问题标题】:How do I close a Server-Send Events connection in Flask?如何在 Flask 中关闭服务器发送事件连接?
【发布时间】:2012-07-22 02:24:02
【问题描述】:

下面已经使用node.js给出了答案。

How to close a "Server-Sent Events"-connection on the server?

但是,如何在 python Flask 中做同样的事情呢?

【问题讨论】:

  • Flask 的作者似乎还没有计划支持它。对于“Server-Sent Events”,最好使用 NodeJS 这样的事件驱动架构。
  • this comment 对类似问题也很有帮助

标签: python flask server-sent-events


【解决方案1】:

请务必注意,您还需要关闭客户端上的连接,否则它将在 retry 超时后尝试重新打开连接。

这让我很困惑,直到我在这里看到这篇文章:https://stackoverflow.com/a/38235218/5180047

来自链接:

这里的问题是服务器意外关闭了连接,而不是在保持打开状态时进行工作。发生这种情况时,客户端会重新发送请求以打开连接并开始流式传输服务器发送的事件。然后服务器一次又一次地关闭连接,导致无限循环。

在我看到这个之后.. 我首选的解决方案是通过产生预期的数据消息来关闭与服务器的连接。

在客户端(浏览器)上

var sse = new EventSource('/sse');
sse.addEventListener('message', function(e) {
  console.log(e);
  var data = e.data;
  if (!data) {
      console.log('no data in event');
      return;
  }
  if (data === 'finished') {
      console.log('closing connection')
      sse.close()
  }
  // my work here based on message
});

我的 Flask 服务器

from flask import Response

@app_api.route("/sse")
def stream():
    def trackerStream():
        finished = check_if_finished()
        while not finished:
            sleep(1) # poll timeout
            if should_send_event():
                yield f"data: {mydata()}\n\n"
            else:
                yield "data: nodata\n\n"

            finished = check_if_finished()

        yield "data: finished\n\n"

    return Response(trackerStream(), mimetype="text/event-stream")

注意:请确保始终以一定的时间间隔从服务器向客户端发送事件。如果用户关闭浏览器,flask 在尝试写入套接字时会出错,并会为您关闭流。如果您没有在某个时间间隔内写入客户端,即使您只是在写入data: nodata\n\n,那么服务器可能会陷入循环。

【讨论】:

  • 这非常有用!我很惭愧地说,我一直在导致服务器生成的 XSS 重定向有效负载,以便在输出完成后将浏览器返回到主页,以避免重新打开连接。在我看到这个答案之前,这是我能想出的摆脱循环的唯一方法。很有帮助。
【解决方案2】:

嗯,这取决于您应用的架构。

让我给你看一个例子(见https://github.com/jkbr/chat/blob/master/app.py这个代码):

def event_stream():
    pubsub = red.pubsub()
    pubsub.subscribe('chat')
    for message in pubsub.listen():
        print message
        yield 'data: %s\n\n' % message['data']

@app.route('/stream')
def stream():
    return flask.Response(event_stream(),
                          mimetype="text/event-stream")

Flask 稳定地向 Redis 请求一条新消息(锁定操作),但是当 Flask 看到流式传输终止时(StopIteration,如果您不是 Python 新手),它就会返回。

def event_stream():
    pubsub = red.pubsub()
    pubsub.subscribe('chat')
    for message in pubsub.listen():
        if i_should_close_the_connection:
            break
        yield 'data: %s\n\n' % message['data']

@app.route('/stream')
def stream():
    return flask.Response(event_stream(),
                          mimetype="text/event-stream")

【讨论】:

  • i_should_close_the_connection 是什么?你能再解释一下吗?
  • 这是一个布尔值。您可以像在 Python 中那样使用 if 语句。
  • 我相信 listen() 是一个阻塞调用。如果没有发布任何内容并且客户端断开连接,则服务器将永远挂起。我遇到了这个问题。
  • 是的,这是一个阻塞调用。您可能希望使用 stdlib 中的超时并使用 pubsub.close() 关闭连接。它应该工作,但我还没有检查。
【解决方案3】:

我也遇到了同样的问题,终于找到了如下解决方案here

import time
from flask import Flask, Response, stream_with_context

app = Flask(__name__)

@app.route('/stream')
def stream():
    def gen():
        try:
            i = 0
            while True:
                data = 'this is line {}'.format(i)
                print(data)
                yield data + '<br>'
                i += 1
                time.sleep(1)
        except GeneratorExit:
            print('closed')

    return Response(stream_with_context(gen()))

您可以通过订阅 Redis 的侦听器来启动该方法。而不是time.sleep(1),您可以等待Redis 的listen() 方法返回一个值。您可以取消订阅 Redis,而不是 print('closed')。剩下的唯一问题是GeneratorExit 异常仅在屈服值发送到客户端时引发。因此,如果 Redis 的listen() 永远不会结束,那么您将永远不会发现连接已断开。

【讨论】:

  • 我花了一段时间才意识到在浏览器关闭后服务器可能会保持连接打开一段时间。 (在我的例子中,服务器在大约 150 秒后才关闭连接。)重点是,它确实 工作! :)
猜你喜欢
  • 1970-01-01
  • 2018-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多