团队准备开发 QA 工具平台,一方面锻炼编程能力,一方面是提高团队的工作效率,如可测性、数据构造等(避免仅仅是写一些重复的接口用例)。团队成员的现状如下:
根据上述的背景来看,在进行整体技术栈选型的时候以下两点比较重要。
在进行后端框架的选型之前,先简单的对框架进行一些了解。
框架(Framework)是一个框子——指其约束性,也是一个架子——指其支撑性
它本身一般不完整到可以解决特定问题,其主要的特性是为了扩展各种功能已满足用户的需求。
Python 的常用 Web 框架挺多,但在 Github 上比较火的有 Django、Flask(Tornado 与前两者的 star 数量基本已经相差好几倍了)。
诞生于 2003 年,相对于其他 WEB 框架来说,它主要优点是功能齐全,缺点则是模块之间紧密耦合,开发者需要学习 Django 自己定义的这一整套技术。
诞生于 2010 年,其定义就是面向简单需求和小型应用的微框架 (啥叫微框架,就是毛坯房的意思。给你个毛胚房,你自己装修去),这样的好处就是具有较高的扩展性。
诞生于2009年9月10日,其主要特点是:提供了异步 I/O 支持、超时事件处理。
Full-Stack Web 框架:框架功能很全,不用自己造轮子,比如 (cache、session、登陆、auth 授权等等) 以及它强大的中间件,提供全方案 Web 开发支持。比如Django 框架。
当然功能强大和全面的反面就是有点复杂(相对的),不太灵活。所以 Django 上手要慢一点,自己造一个轮子替换 Django 某些内置功能或者使用第三方功能时不太灵活。
Non Full-Stack Web 框架:框架小巧,灵活,很多功能需要开发者以插件的形式向里安装,也可以自己定制,比如Flask 框架。
Asynchronous 异步框架:框架本身的速度比较快,I/O 性能吞吐高并发,当然异步编程的理解难度要大一点。像Tornado和Sanic 框架。
截至2019年9月2日,Flask 在 Github 上的星数是 46179 颗,Django 的 Github 星数是 43806 颗,两者几乎难分伯仲,其它 Python Web 框架与 Flask 和 Django 星数相差甚远。考虑到 Django 早发布 5 年,而 Flask 在星数上还领先 2000 多颗,由此可以得知 Flask 当前略微占优。
选 Flask 框架的原因主要基于如下几个点:
简约的设计哲学 一一 Less is more
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
通俗易懂的 快速入门。
装饰器表示路由。(面向切面编程-JAVA 中的 AOP)。
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/add', methods=['POST'])
def compute_add():
params = request.get_json().get('params')
if not params or not isinstance(params, dict):
return "params is error"
else:
return str(params.setdefault('a', 0) + params.setdefault('b', 0))
通过本地 Debug 可以明显发现,代码启动后自动通过 app.route('xxx')
就自动将前端访问的路由地址与后端的代码给绑定到了一起,并且在后续的请求中,通过装饰器自动定位到具体的后端方法。
蓝图 一一 Blueprint
常见的情况:当功能多了后就会出现如下代码的样式,很多个路由都在一个文件中,维护以及可读性都很差。这个时候就需要方便的管理这些路由。
@app.route('/add')
def hello_world1():
return 'Hello World!'
@app.route('/user_info')
def hello_world2():
return 'Hello World!'
@app.route('/compute')
def hello_world3():
return 'Hello World!'
...
蓝图便类似一个有效的菜单栏,通过将不同的功能的子菜单在不同的文件中单独维护,然后集中在通过 app.register\_blueprint(xxx)
注册到代码中。
from flask import Blueprint, jsonify
compute_method_api = Blueprint('compute_method', __name__)
@user7oute("/compute_add")
def get_sonar_error_info():
return jsonify(1 + 1)
通过上面介绍,在需要开发的平台不太复杂的情况下,Flask比较符合新手作为快速入门的框架(个人意见)。
Flask 是一种使用 Python 编写的轻量级的 Web 框架,WSGI 工具采用 Werkzeug,模板引擎使用 Jinja2,下图表示 WSGI 的一个过程。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
(在不去了解内部原理的情况下)光从代码来看,仅仅做了如下几点:
- 首先,导入了 Flask 类,这个类的对象将会是我们的 WSGI 应用程序。
- 接下来,创建一个该类的实例对象,第一个参数是应用模块或者包的名称。这样 Flask 才知道到哪去找模板、静态文件等等。详情见 Flask 的文档。
- 然后,我们使用 route() 装饰器告诉 Flask 什么样的 URL 能触发我们的函数。这个函数的名字也在生成 URL 时被特定的函数采用,这个函数返回我们想要显示在用户浏览器中的信息。
- 最后我们用 run() 函数来让应用运行在本地服务器上。
那具体上面的 Demo 代码实现原理是如何呢?具体分为 3 个步骤,分别对应着上面的代码层面的逻辑: 见如下图:
self.static_url_path:用于改变 url 的 path,默认静态文件放在 static 下面,所以 url 是 static/filename 。
app = Flask(__name__, static_url_path="/static/change")
# 如上指定static_url_path路径后那么默认的static/filename就访问不到静态资源了,必须通过static/change/filename才能访问到。
self.static_folder: 用来改变静态资源目录,默认是 static 目录下。
app = Flask(__name__, static_folder="my_static")
# static_folder="my_static" 那么默认的static/filename就访问不到静态资源了,必须通过my_static/filename才能访问到静态资源。
self.blueprints: 用于更好的管理多个 Api 文件。(更多相关可以自行搜索Flask 蓝图)
举个例子:如果把所有的路由都如 Demo 代码里面的 app.route 一样写到一个文件中,那随着项目的增加,此文件的维护性会越来越差,所以通过先将不同的模块都分别写到对应的文件中,然后通过蓝图的注册到 Flask 实例对象中的self.blueprints。
self.url_map: 用于存储所有的路由规则。
基本的原理就是通过解析所有带有route装饰器的方法,通过将 rule 与 function_name 进行一一绑定并且存储到self.url_map中,大概的存储格式如下图:(rule 为 route('xxx') 中的 xxx, endpoint 为绑定的方法名,像 Demo 代码中 rule 为 '/', endpoint 为 'hello_world')
从源码中发现 flask 启动 server 的时候实际就是直接使用的 Werkzeug.serving 的 run_simple(...),在这里可以简单的理解内部启动了一个 BaseWSGIServer。
在浏览器中输入 http://0.0.0.0:5000/
就会返回 Hello World 了。那 Flask 又是如何处理的浏览器发起的请求的呢?大概原理如下图:
整个处理请求的逻辑简单的可以看分为两部分:
简单点说就是创建一个线程,并且为此次请求实例化一个通用处理器 BaseRequestHandler
用于初始化此次请求的信息等,然后直接调用 Flask.app 去执行该请求,最终通过 start_response 对结果进行相应处理后返回。
Flask.app
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
下图梳理了 Flask 框架的一个整体流程图(有兴趣的可以更深入的去了解下相关内容)。