背景

​ 团队准备开发 QA 工具平台,一方面锻炼编程能力,一方面是提高团队的工作效率,如可测性、数据构造等(避免仅仅是写一些重复的接口用例)。团队成员的现状如下:

根据上述的背景来看,在进行整体技术栈选型的时候以下两点比较重要。

框架选型

在进行后端框架的选型之前,先简单的对框架进行一些了解。

框架(Framework)是一个框子——指其约束性,也是一个架子——指其支撑性

它本身一般不完整到可以解决特定问题,其主要的特性是为了扩展各种功能已满足用户的需求。

框架选择

Python 的常用 Web 框架挺多,但在 Github 上比较火的有 DjangoFlaskTornado 与前两者的 star 数量基本已经相差好几倍了)。

诞生于 2003 年,相对于其他 WEB 框架来说,它主要优点是功能齐全,缺点则是模块之间紧密耦合,开发者需要学习 Django 自己定义的这一整套技术。

诞生于 2010 年,其定义就是面向简单需求和小型应用的微框架 (啥叫微框架,就是毛坯房的意思。给你个毛胚房,你自己装修去),这样的好处就是具有较高的扩展性。

诞生于2009年9月10日,其主要特点是:提供了异步 I/O 支持、超时事件处理。

不同框架的区别

为什么选择 Flask

      截至2019年9月2日,Flask 在 Github 上的星数是 46179 颗,Django 的 Github 星数是 43806 颗,两者几乎难分伯仲,其它 Python Web 框架与 Flask 和 Django 星数相差甚远。考虑到 Django 早发布 5 年,而 Flask 在星数上还领先 2000 多颗,由此可以得知 Flask 当前略微占优。

选 Flask 框架的原因主要基于如下几个点:

  1. 简约的设计哲学 一一 Less is more

    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    if __name__ == '__main__':
        app.run()
    
  2. 通俗易懂的 快速入门

  3. 装饰器表示路由。(面向切面编程-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') 就自动将前端访问的路由地址与后端的代码给绑定到了一起,并且在后续的请求中,通过装饰器自动定位到具体的后端方法。

  4. 蓝图 一一 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 框架介绍

​ Flask 是一种使用 Python 编写的轻量级的 Web 框架,WSGI 工具采用 Werkzeug,模板引擎使用 Jinja2,下图表示 WSGI 的一个过程。




首先从官网的一个最小的 Flask 应用进行分析,运行如下代码:
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)

(在不去了解内部原理的情况下)光从代码来看,仅仅做了如下几点:

  1. 首先,导入了 Flask 类,这个类的对象将会是我们的 WSGI 应用程序。
  2. 接下来,创建一个该类的实例对象,第一个参数是应用模块或者包的名称。这样 Flask 才知道到哪去找模板、静态文件等等。详情见 Flask 的文档。
  3. 然后,我们使用 route() 装饰器告诉 Flask 什么样的 URL 能触发我们的函数。这个函数的名字也在生成 URL 时被特定的函数采用,这个函数返回我们想要显示在用户浏览器中的信息。
  4. 最后我们用 run() 函数来让应用运行在本地服务器上。

那具体上面的 Demo 代码实现原理是如何呢?具体分为 3 个步骤,分别对应着上面的代码层面的逻辑: 见如下图:



  1. 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才能访问到。
    
  2. self.static_folder: 用来改变静态资源目录,默认是 static 目录下。

    app = Flask(__name__, static_folder="my_static")
    # static_folder="my_static" 那么默认的static/filename就访问不到静态资源了,必须通过my_static/filename才能访问到静态资源。
    
  3. self.blueprints: 用于更好的管理多个 Api 文件。(更多相关可以自行搜索Flask 蓝图

    举个例子:如果把所有的路由都如 Demo 代码里面的 app.route 一样写到一个文件中,那随着项目的增加,此文件的维护性会越来越差,所以通过先将不同的模块都分别写到对应的文件中,然后通过蓝图的注册到 Flask 实例对象中的self.blueprints

  4. self.url_map: 用于存储所有的路由规则。

基本的原理就是通过解析所有带有route装饰器的方法,通过将 rulefunction_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 对结果进行相应处理后返回。

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 框架的一个整体流程图(有兴趣的可以更深入的去了解下相关内容)。

分享内容_photo_2.png

参考链接


↙↙↙阅读原文可查看相关链接,并与作者交流