使用 Python 和 Flask 流式传输数据

Posted

技术标签:

【中文标题】使用 Python 和 Flask 流式传输数据【英文标题】:Streaming data with Python and Flask 【发布时间】:2012-11-03 09:54:41 【问题描述】:

我似乎无法弄清楚如何使用 Flask 的流媒体。这是我的代码:

@app.route('/scans/')
def scans_query():
    url_for('static', filename='.*')
    def generate():
        yield render_template('scans.html')
        for i in xrange(50):
            sleep(.5)
            yield render_template('scans.html', **locals())
    return Response(stream_with_context(generate()))

在我的模板中:

<p>% i %</p>

我希望在页面上看到每半秒更改一次的计数器。相反,我得到的最接近的是在下一行打印出每个数字的页面。

【问题讨论】:

【参考方案1】:

我认为如果你要使用这样的模板,你可能需要使用这里给出的stream_template 函数:http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates

我没有对此进行测试,但它可能看起来像:

def stream_template(template_name, **context):
    app.update_template_context(context)
    t = app.jinja_env.get_template(template_name)
    rv = t.stream(context)
    rv.enable_buffering(5)
    return rv

@app.route('/scans/')
def scans_query():
    url_for('static', filename='.*')
    def generate():
        for i in xrange(50):
            sleep(.5)
            yield i
    return Response(stream_template('scans.html', i=generate()))

【讨论】:

使用此解决方案浏览 localhoast:5000/scans 在我的计算机上出现以下错误:jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'i'. 有关如何成功运行它的任何建议? @SpeedCoder5 这段代码是在 2012 年写的。我怀疑最近的 Flask 版本和 Python 版本需要不同的解决方案或至少有语法更改。【参考方案2】:

要替换页面上的现有内容,您可能需要 javascript,即,您可以发送它或让它为您发出请求,使用长轮询、websockets 等。有很多方法可以做到这一点,这里有一个使用 @ 987654321@:

#!/usr/bin/env python
import itertools
import time
from flask import Flask, Response, redirect, request, url_for

app = Flask(__name__)

@app.route('/')
def index():
    if request.headers.get('accept') == 'text/event-stream':
        def events():
            for i, c in enumerate(itertools.cycle('\|/-')):
                yield "data: %s %d\n\n" % (c, i)
                time.sleep(.1)  # an artificial delay
        return Response(events(), content_type='text/event-stream')
    return redirect(url_for('static', filename='index.html'))

if __name__ == "__main__":
    app.run(host='localhost', port=23423)

在哪里static/index.html

<!doctype html>
<title>Server Send Events Demo</title>
<style>
  #data 
    text-align: center;
  
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
if (!!window.EventSource) 
  var source = new EventSource('/');
  source.onmessage = function(e) 
    $("#data").text(e.data);
  

</script>
<div id="data">nothing received yet</div>

如果连接丢失,浏览器默认在 3 秒内重新连接。如果没有更多要发送的内容,服务器可以返回 404 或仅发送 'text/event-stream' 以外的内容类型以响应下一个请求。即使服务器有更多数据,要在客户端停止,您也可以调用source.close()

注意:如果流不是无限的,则使用其他技术(不是 SSE),例如,发送 javascript sn-ps 来替换文本(无限 &lt;iframe&gt; 技术):

#!/usr/bin/env python
import time
from flask import Flask, Response

app = Flask(__name__)


@app.route('/')
def index():
    def g():
        yield """<!doctype html>
<title>Send javascript snippets demo</title>
<style>
  #data 
    text-align: center;
  
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<div id="data">nothing received yet</div>
"""

        for i, c in enumerate("hello"):
            yield """
<script>
  $("#data").text("i c")
</script>
""".format(i=i, c=c)
            time.sleep(1)  # an artificial delay
    return Response(g())


if __name__ == "__main__":
    app.run(host='localhost', port=23423)

我在此处内联了 html,以表明它没有更多内容(没有魔法)。这里和上面一样,但是使用了模板:

#!/usr/bin/env python
import time
from flask import Flask, Response

app = Flask(__name__)


def stream_template(template_name, **context):
    # http://flask.pocoo.org/docs/patterns/streaming/#streaming-from-templates
    app.update_template_context(context)
    t = app.jinja_env.get_template(template_name)
    rv = t.stream(context)
    # uncomment if you don't need immediate reaction
    ##rv.enable_buffering(5)
    return rv


@app.route('/')
def index():
    def g():
        for i, c in enumerate("hello"*10):
            time.sleep(.1)  # an artificial delay
            yield i, c
    return Response(stream_template('index.html', data=g()))


if __name__ == "__main__":
    app.run(host='localhost', port=23423)

在哪里templates/index.html

<!doctype html>
<title>Send javascript with template demo</title>
<style>
  #data 
    text-align: center;
  
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<div id="data">nothing received yet</div>
% for i, c in data: %
<script>
  $("#data").text(" i   c ")
</script>
% endfor %

【讨论】:

您提供的演示(特别是我正在玩 SSE 演示)仅适用于单个客户端。如果我打开一个新的浏览器窗口并尝试访问页面流数据,则在我关闭或停止上一页之前什么都不会发生。然后,计数器从 0 开始重新启动。您将如何修改它,以便任何尝试访问它的客户端都可以看到相同的数据/计数器,从应用程序启动的时间开始计数?我假设您必须在单独的线程中运行计数器,但我不确定如何实现。 @DavidMarx:至少有两个问题:(1)flask中如何支持多个并发客户端? — 答案:与任何 wsgi 应用程序一样,例如,使用 gunicorn (2) 如何为多个客户端提供对同一个柜台的访问权限? — 就像您在任何服务器程序中提供对共享数据的访问一样,例如,假设一个工作人员:定义全局迭代器并在循环中调用 next(it)。无论如何,这些都是单独的问题。针对您的特定问题提出一个新问题。 尝试第一个示例读取日志文件并在 html 中显示,但 html 仅打印第一行.. 但 print() 标有 **** 打印所有行 def events(): with open('E :/Study/loggerApp/templates/job.log') as f: data=""; while True: data=f.read() yield "data: "+(data) if data!="": ****print(data)**** time.sleep(1) return Response(events() , content_type='文本/事件流') @Karan:我很惊讶代码在 8 年后仍然有效。要发送多行数据,您可以尝试将每一行作为单独的事件发送,或者仅将数据作为 json 发送(在这种情况下,换行符被转义)。

以上是关于使用 Python 和 Flask 流式传输数据的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jinja 进行 Flask 模板流式传输

当使用flask进行流式传输时,只有opencv的VIDEO错误。

在流式传输网络摄像头时定期拍照

通过 MQ 流式传输音频流(可扩展性)

如何在 Flask Server 上的 Google Pubsub 订阅回调中屈服以进行流式传输

OpenCV:将视频流式传输到网页浏览器/HTML页面