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