使用Python和Flask流式传输数据

Aar*_*eba 36 python flask

我似乎无法弄清楚如何使用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()))
Run Code Online (Sandbox Code Playgroud)

在我的模板中:

<p>{% i %}</p>
Run Code Online (Sandbox Code Playgroud)

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

jfs*_*jfs 57

要替换页面上的现有内容,您可能需要javascript,即,您可以发送它或使其为您发出请求,使用长轮询,websockets等.有很多方法可以做到这一点,这里有一个使用服务器发送事件:

#!/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)
Run Code Online (Sandbox Code Playgroud)

地点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>
Run Code Online (Sandbox Code Playgroud)

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

注意:如果流不是无限的,那么使用其他技术(而不是SSE),例如,发送javascript片段来替换文本(无限<iframe>技术):

#!/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)
Run Code Online (Sandbox Code Playgroud)

我在这里列出了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)
Run Code Online (Sandbox Code Playgroud)

地点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 %}
Run Code Online (Sandbox Code Playgroud)

  • 您提供的演示(特别是我正在玩 SSE 演示)仅适用于单个客户端。如果我打开一个新的浏览器窗口并尝试访问页面流数据,则在关闭或停止上一页之前不会发生任何事情。然后,计数器从 0 开始重新开始。您将如何重新设计它,以便任何尝试访问它的客户端都会看到相同的数据/计数器,从应用程序启动的时间开始计数?我假设您必须在单独的线程中运行计数器,但我不确定如何实现这一点。 (2认同)
  • @DavidMarx:至少有两个问题:(1)如何在flask中支持多个并发客户端?— 答案:与您对任何 wsgi 应用程序执行的方式相同,例如,使用 gunicorn (2) 如何为多个客户端提供对同一个计数器的访问?— 与您在任何服务器程序中提供对共享数据的访问的方式相同,例如,假设一个工作人员:定义全局迭代器并在循环中调用 `next(it)`。无论如何,这些都是不同的问题。针对您的特定问题提出一个新问题。 (2认同)

aez*_*ell 6

我想如果您要使用这样的模板,您可能需要使用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()))
Run Code Online (Sandbox Code Playgroud)