Отображение данных, передаваемых из представления колбы, по мере их обновления

#python #flask #request

Вопрос:

У меня есть представление, которое генерирует данные и передает их в режиме реального времени. Я не могу понять, как отправить эти данные в переменную, которую я могу использовать в своем HTML-шаблоне. Мое текущее решение просто выводит данные на пустую страницу по мере их поступления, что работает, но я хочу включить их в большую страницу с форматированием. Как обновить, отформатировать и отобразить данные по мере их передачи на страницу?

 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)   'lt;br/gt;n'  return flask.Response(inner(), mimetype='text/html')  app.run(debug=True)  

Комментарии:

1. Вы ожидаете, что на экране будет выводиться непрерывный поток выходных значений?

2. ДА. Приведенный выше цикл for имитирует более длительный процесс. В принципе, мне нужна обратная связь, похожая на печать, пока она работает.

Ответ №1:

Вы можете передавать данные в ответ, но вы не можете динамически обновлять шаблон так, как вы описываете. Шаблон отображается один раз на стороне сервера, а затем отправляется клиенту.

Одним из решений является использование 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")  
 lt;pgt;This is the latest output: lt;span id="latest"gt;lt;/spangt;lt;/pgt; lt;pgt;This is all the output:lt;/pgt; lt;ul id="output"gt;lt;/ulgt; lt;scriptgt;  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); lt;/scriptgt;  

An lt;iframegt; можно использовать для отображения потоковых выходных данных 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('lt;link rel=stylesheet href="{{ url_for("static", filename="stream.css") }}"gt;')   for i in range(500):  yield render_template_string("lt;pgt;{{ i }}: {{ s }}lt;/pgt;n", i=i, s=sqrt(i))  sleep(1)   return app.response_class(generate())  
 lt;pgt;This is all the output:lt;/pgt; lt;iframe src="{{ url_for("stream") }}"gt;lt;/iframegt;  

Ответ №2:

С опозданием на 5 лет, но на самом деле это можно сделать так, как вы изначально пытались это сделать, javascript совершенно не нужен (Правка: автор принятого ответа добавил раздел iframe после того, как я написал это). Вам просто нужно включить встраивание выходных данных в качестве lt;iframegt; :

 from flask import Flask, render_template, Response import time, math  app = Flask(__name__)  @app.route('/content') # render the content a url differnt from index def content():  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)   'lt;br/gt;n'  return Response(inner(), mimetype='text/html')  @app.route('/') def index():  return render_template('index.html.jinja') # render a template at the index. The content will be embedded in this template  app.run(debug=True)  

Затем файл «index.html.jinja» будет содержать lt;iframegt; URL-адрес содержимого в качестве src, что будет выглядеть примерно так:

 lt;!doctype htmlgt; lt;headgt;  lt;titlegt;Titlelt;/titlegt; lt;/headgt; lt;bodygt;  lt;divgt;  lt;iframe frameborder="0" noresize="noresize"  style='background: transparent; width: 100%; height:100%;' src="{{ url_for('content')}}"gt;lt;/iframegt;  lt;/divgt; lt;/bodygt;    

При рендеринге предоставленные пользователем данные render_template_string() следует использовать для рендеринга контента, чтобы избежать атак с использованием инъекций. Однако я исключил это из примера, потому что это добавляет дополнительную сложность, выходит за рамки вопроса, не имеет отношения к оператору, поскольку он не передает данные, предоставленные пользователем, и не будет иметь отношения к подавляющему большинству людей, которые видят этот пост, поскольку потоковая передача данных, предоставленных пользователем,-это крайний случай, который мало кому когда-либо придется делать.