-
Notifications
You must be signed in to change notification settings - Fork 382
Why PyWebIO?
在日常开发工作中,经常会遇到这样的一个问题:一个功能可以通过一个脚本(控制台程序)快速实现,但为了使用方便又希望通过Web方式提供服务。
将脚本代码转换为Web应用会面临以下几个困难:
- 需要编写额外的前端代码来实现界面。
一般至少要写两个页面,一个是输入表单,另一个是结果页。如果原脚本里的输入不能整合到一个表单里(比如需要依据一些输入项的内容来决定其他一些输入项),那么还需要编写更多的表单页面。 - 由于Http协议的无状态性,需要在各个后端接口之间转递状态,并且需要额外的代码检查状态的合法性。
比如,当整个业务流程需要提交多个表单时,在Web开发中一般会通过session机制或前端的<input type="hidden">
机制保存用户之前输入的数据。使用Session机制需要小心地进行状态校验和清理(否则会出现诸如验证码可重复使用等漏洞),而<input type="hidden">
机制也容易被恶意修改表单中保存的隐藏数据。 - Web应用在单次HTTP请求中,无法实现实时输出,也加大了将脚本代码转换为Web应用的难度。
比如控制台程序若要进行一些耗时操作,可以直接阻塞在主线程中,只需要周期性打印些日志就可以,而在Web应用中,耗时操作通常需要离线完成,前端需要定时轮询来实现任务进度的实时展示。
如果你非常认同上面几点,你可以直接跳到"PyWebIO的解决方案"一节;如果你对上面几点还有疑惑,下面会通过一个具体的例子来说明。
假设现在要开发一个学生返校疫情风险审查的应用。具体业务流程是,首先学生提交基本的信息,然后判断学生所在地区是否属于高风险地区, 如果属于,还需要上传7日内的核酸检测阴性证明。最后,根据学生所在的学院为学生展示相应的入学安排。
其中,已经实现了一些函数: is_dangerous(address)
用于检测地址address
是否为高风险地区;get_schedule(academy)
用于获取academy
学院的入学安排信息;save()
用于保存用户提交的数据。
如果使用控制台程序实现这个流程,代码逻辑可以非常清晰。以下是Python实现:
stuid = input("请输入学号")
name = input("请输入姓名")
academy = input("请输入学院")
address = input("请输入地址")
# todo: 用户输入校验
covid_test_res = None
if is_dangerous(address):
covid_test_res = input("请提交核酸检测阴性证明")
# todo: 用户输入校验
print("入学安排")
print(get_schedule(academy))
save(stuid, name, academy, address, covid_test_res)
注:以上代码省略了用户数据校验的逻辑
如果要将上述流程实现为Web应用,需要分别编写前端页面和后端接口。
前端需要3个页面,一个是基本信息的提交表单页(basic_info.html
),一个是核酸检测提交页面(covid_test.html
),最后一个是入学安排信息展示页面(schedule_info.html
)。
后端需要编写两个接口,一个是接收学生信息的接口,一个是接收核酸检测的接口。两个接口的Flask实现如下所示:
from flask import Flask, request, session, render_template
app = Flask(__name__)
@app.route('/basic_info') # 学生信息接提交口
def basic_info():
# todo: 校验用户提交的数据
save(request.form)
if is_dangerous(request.form['address']):
session['academy'] = request.form['academy'] # 保存状态
return render_template('covid_test.html')
schedule_info = get_schedule(request.form['academy'])
return render_template('schedule_info.html', data=schedule_info)
@app.route('/covid_test') # 核酸检测提交接口
def covid_test():
if 'academy' not in session:
return error() # 不合法的状态
# todo: 校验用户提交的数据
save(request.form['covid_test_res'])
schedule_info = get_schedule(session['academy'])
return render_template('schedule_info.html', data=schedule_info)
if __name__ == '__main__':
app.run()
首先,从直观上来看,Web服务版的代码就要比脚本版复杂,逻辑看起来也比较吃力。
在Web服务版的代码中,由于第二个接口也需要返回入学安排,而获取入学安排所需要的学院信息是在第一个接口中提交的,所以在第一个接口中需要将学院信息保存在session中。由于在技术上完全可以不请求第一个接口而直接请求第二个接口,所以在第二个接口中还需要额外校验session中是否含有学院信息。
上述Web代码还可能存在数据不一致的问题,因为数据是分别在两个接口中存储的,可能出现用户没有走完整个业务流程,导致仅储存了部分数据,而若把数据都放到最后一步进行存储,又需要在前面的接口中不断地将中间数据往后传递。
另外,假设is_dangerous(address)
调用十分耗时,如果还使用上例的Flask代码,当用户提交基本信息后,页面将会一直加载中,用户体验很不友好。
而终端程序只需要在调用is_dangerous(address)
前输出一些加载提示,就不至于让用户以为程序无响应。
使用PyWebIO可以快速将脚本转换成Web应用,只需要将脚本中的输入输出函数替换成PyWebIO提供的输入输出函数,就可以将脚本中的交互由终端转换为浏览器上:
from pywebio.input import input, file_upload, input_group
from pywebio.output import put_text
stu_info = input_group('基本信息', [
input("学号", name='stuid'),
input("姓名", name='name'),
input("学院", name='academy'),
input("地址", name='address'),
], validate=...) # todo: 用户输入校验
covid_test_res = None
if is_dangerous(stu_info['address']):
covid_test_res = file_upload("请提交核酸检测阴性证明")
# todo: 用户输入校验
put_text("入学安排")
put_text(get_schedule(stu_info['academy']))
save(stu_info, covid_test_res)
脚本执行后,会自动打开浏览器,用户在浏览器上完成原来的交互:
而将更新后的脚本代码包装到一个函数中,并传入start_server()
中就可以将功能作为Web应用来提供了:
from pywebio.input import input, file_upload, input_group
from pywebio.output import put_text
from pywebio import start_server
def main():
stu_info = input_group('基本信息', [
input("学号", name='stuid'),
input("姓名", name='name'),
input("学院", name='academy'),
input("地址", name='address'),
], validate=...) # todo: 用户输入校验
covid_test_res = None
if is_dangerous(stu_info['address']):
covid_test_res = file_upload("请提交核酸检测阴性证明")
# todo: 用户输入校验
put_text("入学安排")
put_text(get_schedule(stu_info['academy']))
save(stu_info, covid_test_res)
start_server(main, port=80)
使用PyWebIO编写Web应用不存在上文描述的几种问题,因为从代码编写逻辑上看,PyWebIO应用还是延续了控制台程序的编写方式,只是应用的交互媒介则由终端变成了浏览器。
也因为如此,比起终端程序,PyWebIO可以输出更多样的内容(比如图片、图表等),因此PyWebIO非常适合快速构建基于输入输出进行交互的Web应用(或基于浏览器的本地GUI应用)。另一方面,相比于在本地运行脚本,通过Web访问服务也免去了安装依赖的麻烦,所以PyWebIO也很适合写一些在线小工具。
此外,PyWebIO还提供了布局、事件绑定、协程、与Web框架集成等特性,让应用的编写更便捷。
文档:https://pywebio.readthedocs.io/ (文档里的绝大部分代码示例都有在线演示的链接)
下面是一些使用PyWebIO编写的Demo和应用: