在更新时显示从 Flask 视图流式传输的数据 [英] Display data streamed from a Flask view as it updates

查看:28
本文介绍了在更新时显示从 Flask 视图流式传输的数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个视图可以生成数据并实时传输数据.我不知道如何将这些数据发送到我可以在 HTML 模板中使用的变量.我当前的解决方案只是在数据到达时将数据输出到空白页面,这可行,但我想将其包含在带有格式的更大页面中.如何在数据流式传输到页面时更新、格式化和显示数据?

I have a view that generates data and streams it in real time. I can't figure out how to send this data to a variable that I can use in my HTML template. My current solution just outputs the data to a blank page as it arrives, which works, but I want to include it in a larger page with formatting. How do I update, format, and display the data as it is streamed to the page?

import flask
import time, math

app = flask.Flask(__name__)

@app.route('/')
def index():
    def inner():
        # simulate a long process to watch
        for i in range(500):
            j = math.sqrt(i)
            time.sleep(1)
            # this value should be inserted into an HTML template
            yield str(i) + '<br/>
'
    return flask.Response(inner(), mimetype='text/html')

app.run(debug=True)

推荐答案

您可以在响应中流式传输数据,但不能按照您描述的方式动态更新模板.模板在服务器端渲染一次,然后发送到客户端.

You can stream data in a response, but you can't dynamically update a template the way you describe. The template is rendered once on the server side, then sent to the client.

一种解决方案是使用 JavaScript 读取流式响应并在客户端输出数据.使用 XMLHttpRequest 向将流式传输数据的端点发出请求.然后定期从流中读取,直到完成.

One solution is to use JavaScript to read the streamed response and output the data on the client side. Use XMLHttpRequest to make a request to the endpoint that will stream the data. Then periodically read from the stream until it's done.

这引入了复杂性,但允许直接更新页面并完全控制输出的外观.以下示例通过显示当前值和所有值的日志来演示.

This introduces complexity, but allows updating the page directly and gives complete control over what the output looks like. The following example demonstrates that by displaying both the current value and the log of all values.

这个例子假设了一个非常简单的消息格式:一行数据,后跟一个换行符.只要有一种方法可以识别每条消息,就可以根据需要进行复杂处理.例如,每个循环都可以返回一个客户端解码的 JSON 对象.

This example assumes a very simple message format: a single line of data, followed by a newline. This can be as complex as needed, as long as there's a way to identify each message. For example, each loop could return a JSON object which the client decodes.

from math import sqrt
from time import sleep
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/stream")
def stream():
    def generate():
        for i in range(500):
            yield "{}
".format(sqrt(i))
            sleep(1)

    return app.response_class(generate(), mimetype="text/plain")

<p>This is the latest output: <span id="latest"></span></p>
<p>This is all the output:</p>
<ul id="output"></ul>
<script>
    var latest = document.getElementById('latest');
    var output = document.getElementById('output');

    var xhr = new XMLHttpRequest();
    xhr.open('GET', '{{ url_for('stream') }}');
    xhr.send();
    var position = 0;

    function handleNewData() {
        // the response text include the entire response so far
        // split the messages, then take the messages that haven't been handled yet
        // position tracks how many messages have been handled
        // messages end with a newline, so split will always show one extra empty message at the end
        var messages = xhr.responseText.split('
');
        messages.slice(position, -1).forEach(function(value) {
            latest.textContent = value;  // update the latest value in place
            // build and append a new item to a list to log all output
            var item = document.createElement('li');
            item.textContent = value;
            output.appendChild(item);
        });
        position = messages.length - 1;
    }

    var timer;
    timer = setInterval(function() {
        // check the response for new data
        handleNewData();
        // stop checking once the response has ended
        if (xhr.readyState == XMLHttpRequest.DONE) {
            clearInterval(timer);
            latest.textContent = 'Done';
        }
    }, 1000);
</script>