您可以在响应中流式传输数据,但不能按照您描述的方式动态更新模板。模板在服务器端渲染一次,然后发送到客户端。
一种解决方案是使用 JavaScript 读取流式响应并在客户端输出数据。用于XMLHttpRequest
向将流式传输数据的端点发出请求。然后定期从流中读取,直到完成。
这引入了复杂性,但允许直接更新页面并完全控制输出的外观。以下示例通过显示当前值和所有值的日志来演示。
这个例子假设了一个非常简单的消息格式:一行数据,后跟一个换行符。只要有一种方法可以识别每条消息,就可以根据需要进行复杂处理。例如,每个循环都可以返回一个客户端解码的 JSON 对象。
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 "{}\n".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('\n');
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>
An<iframe>
可用于显示流式 HTML 输出,但它有一些缺点。框架是一个单独的文档,这增加了资源的使用。由于它仅显示流式数据,因此可能不容易像页面的其余部分一样对其进行样式设置。它只能附加数据,因此长输出将呈现在可见滚动区域下方。它不能响应每个事件修改页面的其他部分。
index.html
使用指向stream
端点的框架呈现页面。该框架的默认尺寸相当小,因此您可能需要进一步设置样式。使用render_template_string
,它知道转义变量,为每个项目呈现 HTML(或render_template
与更复杂的模板文件一起使用)。可以生成初始行以首先在框架中加载 CSS。
from flask import render_template_string, stream_with_context
@app.route("/stream")
def stream():
@stream_with_context
def generate():
yield render_template_string('<link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}">')
for i in range(500):
yield render_template_string("<p>{{ i }}: {{ s }}</p>\n", i=i, s=sqrt(i))
sleep(1)
return app.response_class(generate())
<p>This is all the output:</p>
<iframe src="{{ url_for("stream") }}"></iframe>