我正在制作我的第一个 Flask/python 网络应用程序。该应用程序最初显示邀请用户填写的表单,然后他们单击“提交”按钮,然后服务器运行模拟并创建一个带有显示结果的图形的 PNG 文件,最后页面被重新绘制显示的图形。我的python代码大致是这种形式:
# flask_app.py
@app.route("/", methods=["POST", "GET"])
def home():
if request.method == 'POST':
# bunch of request.form things to scoop the contents of the form
if form_answers_all_good:
for i in range(huge_number):
# some maths
# create png file with results
return render_template("index.htm", foo=bar)
Run Code Online (Sandbox Code Playgroud)
程序运行良好,但huge_number
循环可能需要几十秒。所以我想要的是某种进度指示器——它不必是一个漂亮的动画——即使是百分比进度的字符串读数也可以。
大概我可以将我的 for 循环更改为类似...
for i in range(huge_number):
# some maths
percentage_done = str(i * 100/huge_number)
Run Code Online (Sandbox Code Playgroud)
然后以某种方式在客户端安排阅读(民意调查?),percentage_done
以便我输入以下内容:
Completed {% percentage_done %}% so far.
Run Code Online (Sandbox Code Playgroud)
在我的index.htm
. 顺便说一句,我对 Javascript、AJAX 之类的知识或想到它,几乎所有客户端(除了 HTML)的知识都是初学者级别。
我已经看到很多对类似问题的解释,但通常他们做的事情比我实际需要的要复杂得多,而且由于我缺乏客户端知识,我无法理解它们。例如,某些解决方案可能包含一段代码,而我实际上不知道将它放在哪里,或者我不知道需要先加载其他内容才能使代码段起作用。
编辑:我在pythonanywhere.com
. 包含的模块列表在这里。
编辑: pythonanywhere.com不允许流式传输:-(
你提到你是flask的新手,所以我假设你是flask的新手,但对python很熟悉,而且你对可以使用的东西非常有限,因为你正在使用pythonanywhere。主要的事情之一是它是单线程的,因此很难扩展任何东西。此外,最好坚持使用纯 python,因为在任何地方管理 python 中的依赖项将是一个需要关心的额外问题,最终只使用 python 内置函数来做到这一点是可行的。
我专注于展示一个可行的解决方案,您可以在 pythonanywhere 或本地简单地复制和粘贴,而不是显示代码片段。我会试着去:
(1) 工作解决方案
你可以在这里访问它(我做了很多限制,试图避免人们破坏它)。该解决方案只涉及两个文件,flask_app.py
并且index.html
(1.1) 解决方案代码
“./home/{username}/{flask_foldername}/flask_app.py”
from queue import Queue
import time
import random
import threading
from PIL import Image
import flask
from flask import request
import json
import io
import uuid
import base64
### You create a Queue and start a scheduler, Start flask after that
def run_scheduler(app):
sleep_time = 5
while True:
time.sleep(sleep_time)
print("\n"*5)
print(f'images Completed:{app.images_completed}')
print('-----'*20)
if(app.images_toBe_processed.qsize() > 0):
next_image_name = app.images_toBe_processed.get()
print(f"No Images being processed so scheduler will start processing the next image {next_image_name} from the queue")
app.function_to_process_image(next_image_name, app)
else:
pass
def function_to_process_image(image_name, app):
huge_number = 5
R = random.randint(0,256)
G = random.randint(0,256)
B = random.randint(0,256)
for i in range(huge_number):
# some maths
percentage_done = str((i+1)*100/huge_number)
app.images_processing_status[image_name] = percentage_done
time.sleep(1)
app.images_processing_status[image_name] = str(100.0)
img = Image.new('RGB', (60, 30), color =(R,G,B))
b=io.BytesIO()
img.save(b, "jpeg")
app.images_completed[image_name] = {"status":1,"file": b}
print(f"IC from function: {app.images_completed} **************************")
if app.images_processing_status.get("!!total!!",False): app.images_processing_status["!!total!!"]+= 1
else: app.images_processing_status["!!total!!"] = 1
del app.images_processing_status[image_name]
return 0 #process sucessful
class Webserver(flask.Flask):
def __init__(self,*args,**kwargs):
scheduler_func = kwargs["scheduler_func"]
function_to_process_image = kwargs["function_to_process_image"]
queue_MAXSIZE = kwargs["queue_MAXSIZE"]
del kwargs["function_to_process_image"], kwargs["scheduler_func"], kwargs["queue_MAXSIZE"]
super(Webserver, self).__init__(*args, **kwargs)
self.start_time = time.strftime("%d/%m/%Y %H:%M")
self.queue_MAXSIZE = queue_MAXSIZE
self.active_processing_threads = []
self.images_processing_status = {}
self.images_completed = {}
self.images_toBe_processed = Queue(maxsize=queue_MAXSIZE)
self.function_to_process_image = function_to_process_image
self.scheduler_thread = threading.Thread(target=scheduler_func, args=(self,))
app = Webserver(__name__,
template_folder="./templates",
static_folder="./",
static_url_path='',
scheduler_func = run_scheduler,
function_to_process_image = function_to_process_image,
queue_MAXSIZE = 20,
)
### You define a bunch of views
@app.route("/",methods=["GET"])
def send_index_view():
if not flask.current_app.scheduler_thread.isAlive():
flask.current_app.scheduler_thread.start()
return flask.render_template('index.html',queue_size = flask.current_app.images_toBe_processed.qsize(),
max_queue_size =flask.current_app.queue_MAXSIZE , being_processed=len(flask.current_app.active_processing_threads),
total=flask.current_app.images_processing_status.get("!!total!!",0), start_time=flask.current_app.start_time )
@app.route("/process_image",methods=["POST"])
def receive_imageProcessing_request_view():
image_name = json.loads(request.data)["image_name"]
if(flask.current_app.images_toBe_processed.qsize() >= flask.current_app.queue_MAXSIZE ):
while(not flask.current_app.images_toBe_processed.empty()):
flask.current_app.images_toBe_processed.get()
requestedImage_status = {"name":image_name, "id":uuid.uuid1()}
flask.current_app.images_toBe_processed.put(image_name)
return flask.jsonify(requestedImage_status)
@app.route("/check_image_progress",methods=["POST"])
def check_image_progress():
print(f'Current Image being processed: {flask.current_app.images_processing_status}')
print(f'Current Images completed: {flask.current_app.images_completed}')
image_name = json.loads(request.data)["image_name"]
is_finished = flask.current_app.images_completed \
.get(image_name,{"status":0,"file": ''})["status"]
requestedImage_status = {
"is_finished": is_finished,
"progress": flask.current_app.images_processing_status.get(image_name,"0")
}
return flask.jsonify(requestedImage_status) #images_processing_status[image_name]})
@app.route("/get_image",methods=["POST"])
def get_processed_image():
image_name = json.loads(request.data)["image_name"]
file_bytes = flask.current_app.images_completed[image_name]["file"] #open("binary_image.jpeg", 'rb').read()
file_bytes = base64.b64encode(file_bytes.getvalue()).decode()
flask.current_app.images_completed.clear()
return flask.jsonify({image_name:file_bytes}) #images_processing_status[image_name]})
Run Code Online (Sandbox Code Playgroud)
“./home/{username}/{flask_foldername}/templates/index.html”
<html>
<head>
</head>
<body>
<h5> welcome to the index page, give some inputs and get a random RGB image back after some time</h5>
<h5> Wait 10 seconds to be able to send an image request to the server </h5>
<h5>When the page was loaded there were {{queue_size}} images on the queue to be processed, and {{being_processed}} images being processed</h5>
<h5> The max size of the queue is {{max_queue_size}}, and it will be reseted when reaches it</h5>
<h5>A total of {{total}} images were processed since the server was started at {{start_time}}</h5>
<form>
<label for="name">Image name:</label><br>
<input type="text" id="name" name="name" value="ImageName" required><br>
</form>
<button onclick="send();" disabled>Send request to process image </button>
<progress id="progressBar" value="0" max="100"></progress>
<img style="display:block" />
<script>
window.image_name = "";
window.requests = "";
function send(){
var formEl = document.getElementsByTagName("form")[0];
var input = formEl.getElementsByTagName("input")[0];
var RegEx = /^[a-zA-Z0-9]+$/;
var Valid = RegEx.test(input.value);
if(Valid){
window.image_name = input.value;
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
window.requests = setTimeout(check_image_progress, 3000);
};
xhttp.open("POST", "/process_image", true);
xhttp.send(JSON.stringify({"image_name":input.value}));
var buttonEl = document.getElementsByTagName("button")[0];
buttonEl.disabled = true;
buttonEl.innerHTML = "Image sent to process;only one image per session allowed";
}
else{
alert("input not valid, only alphanumeric characters");
}
}
function check_image_progress(){
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
var progressBarEl = document.getElementsByTagName("progress")[0];
if(progressBarEl.value < result["progress"]){
progressBarEl.value=result["progress"];
} else {}
if(result["is_finished"] == true){
clearTimeout(window.requests);
window.requests = setTimeout(get_image,5);
}
else {
window.requests = setTimeout(check_image_progress, 3000);
}
};
xhttp.open("POST", "/check_image_progress", true);
xhttp.send(JSON.stringify({"image_name":window.image_name}));
}
function get_image(){
var xhttp = new XMLHttpRequest();
xhttp.onload = function() {
result=JSON.parse(xhttp.response)
img_base64 = result[window.image_name];
var progressBarEl = document.getElementsByTagName("progress")[0];
progressBarEl.value=100;
clearTimeout(window.requests);
var imgEl = document.getElementsByTagName("img")[0];
console.log(result)
imgEl.src = 'data:image/jpeg;base64,'+img_base64;
};
xhttp.open("POST", "/get_image", true);
xhttp.send(JSON.stringify({"image_name":window.image_name}));
}
setTimeout(function(){document.getElementsByTagName("button")[0].disabled=false;},100);
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
</script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
(2) 如何复制和创建自己的 webapp
web
(3) 应用程序的主要组成部分
关于 pythonanywhere 的一些细节:
Pythoanywhere 运行 awsgi
来启动你的flask app
,它基本上会app
从 中导入你的flask_app.py
并运行它。
默认情况下wsgi
在您的/home/{username}
文件夹中而不是在 中运行/home/{username}/{flask_folder}
,您可以根据需要更改此设置。
Pythonanywhere 是单线程的,因此您不能依赖将作业发送到后台。
后端需要注意的主要组件:
1) Threads
,Flask
将在主线程中运行wsgi
,我们将运行一个子线程scheduler
,该线程将跟踪Queue
并安排下一个要处理的图像。
2)Flask
类:app,处理用户请求并将处理请求发送给应用程序的组件Queue
3) Queue
,一个队列,用于存储用户处理图像的请求
4) Scheduler
, 决定是否可以运行新函数 process_image 调用的组件,如果可以。它需要在独立Thread
于烧瓶中运行。
5) 将所有这些封装在自定义类中Webserver
以便能够轻松访问(pythonanywhere 使用 wsgi 这使得跟踪本地创建的变量变得困难)
所以看看代码的大图
#lot of imports
+-- 14 lines: from queue import Queue-----------------------------------------------------------------------------------------
# this function will check periodically if there's no images being processed at the moment.
# if no images are being processed check in the queue if there's more images to be processd
# and start the first one in the queue
def run_scheduler(app):
+-- 12 lines: sleep_time = 5 -------------------------------------------------------------------------------------------------
# this function do the math and creates an random RGB image in the end.
def function_to_process_image(image_name, app):
+-- 21 lines: {---------------------------------------------------------------------------------------------------------------
# This class encapsulates all the data structures("state") from our application
# in order to easily access the progress and images information
class Webserver(flask.Flask):
def __init__(self,*args,**kwargs):
+-- 13 lines: scheduler_func = kwargs["scheduler_func"]-----------------------------------------------------------------------
# Here we're instatiating the class
app = Webserver(__name__,
+-- 5 lines: template_folder="./templates",----------------------------------------------------------------------------------
queue_MAXSIZE = 20,
)
### You define a bunch of views
+-- 39 lines: @app.route("/",methods=["GET"]) --------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
前端的主要组成部分:
send
用户点击send request to process image
按钮时触发的函数check_progress
由send function
反复请求 Flask 中的check_progress
视图以获取有关进度的信息触发的函数。处理结束后,我们将删除重复项。get_image
check_progress
处理结束时触发的函数('is_finished' = 1)前端大图:
<html>
<head>
</head>
<body>
<!-- JUST THE INITIAL HTML elements -->
+-- 12 lines: <h5> welcome to the index page, give some inputs and get a random RGB image back after some time</h5>-----------
<script>
window.image_name = "";
window.requests = "";
function send(){
// SEND image process request when click button and set a timer to call periodically check_image_process
+-- 20 lines: var formEl = document.getElementsByTagName("form")[0];----------------------------------------------------------
}
function check_image_progress(){
// SEND a request to get processing status for a certain image_name
+-- 18 lines: var xhttp = new XMLHttpRequest();-------------------------------------------------------------------------------
}
function get_image(){
// SEND a request to get the image when image_status 'is_processed' = 1
+--- 13 lines: var xhttp = new XMLHttpRequest();------------------------------------------------------------------------------
}
setTimeout(function(){document.getElementsByTagName("button")[0].disabled=false;},100);
</script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)