使用 Flask 开发 Web 应用(一)

微框架之“微”

Flask 强调本身是一个用于 Web 开发的微框架。咱们知道,开发 Web 应用主要的工做,就是对一个 Web 请求,接收其请求数据(输入),根据业务逻辑进行处理,而后返回相应的响应结果(输出)。Flask 微框架的“微”字,体如今它专一于上面这个流程的两端,即处理输入数据和生成输出数据。至于中间如何进行处理,那是开发人员的事情,Flask 并未提供便利。python

基本的开发思路

主观感觉上,咱们更多的是将 Flask 当作一个库而不是框架在使用的。这赋予了咱们更大的灵活性,获得输入数据后,如何验证,如何处理,使用什么技术处理,咱们具备极大的自由。只要最终咱们可以用上 Flask 提供的输出工具把结果抛回去就能够了。程序员

Flask 没有强加给开发人员诸如 MVC 或 MTV 这样的设计模式,不少时候,响应一个请求的代码从较高的层面来看,仅仅是编写一个 Flask 称为视图的函数:编程

@app.route('/path/to/here')  # 0. 用修饰器的形式描述路由规则
def perform(**kwargs):
    """请求处理函数"""
    # 1. 围绕 request 对象获取输入数据
    params = fetch_params(request, kwargs)
    # 2. 根据业务须要进行必要的处理
    result = deal_with(params)
    # 3. 使用 Flask 提供的机制,一般是 render_template() 或 jsonify() 生成响应结果
    return create_response(result)

Flask 没有所谓模型层的概念,模板层也不是必需的(虽然从包的依赖上看对 Jinja 的依赖是必需的),可是彻底可使用其它模板技术甚至彻底不使用模板。开发中须要关注的,就是获取输入数据,进行必要的处理,而后输出对应的信息。json

笔者认为,偏偏是如此简单直观的设计哲学,才让 Flask 深受欢迎。这篇文章将从这几个简单的步骤展开,记述笔者本身对如何使用 Flask 的理解。flask

输入数据的获取

从技术的层面,一个请求的输入数据有几个层面的含义:设计模式

  • 它是遵循 HTTP 规范(一般是 HTTP 1.1)及一些扩展(例如咱们可能自行约定特殊的 header 字段)的请求报文。这是背景知识,咱们须要知道 HTTP 请求大概是怎样的形态。api

  • 在 Flask 应用程序中一般表现为“全局”对象 request,它是 flask.Request 类的实例。这是对 HTTP 请求报文的抽象和封装,它提供了设计良好的属性和方法,来表现一个 HTTP 请求。浏览器

注意这个全局是打了引号的。后面咱们会进行讨论,在这里能够粗略的理解在处理单个请求的过程当中这个对象就像是全局变量同样。安全

一般来讲,咱们理解的输入数据应该更加扁平直观一些,它们应该是原始数据类型的某种较简单(列表、字典)的组合,这些数据一般是从 request 对象中提取的。通常来讲,存在几个来源:服务器

  • 请求的路径可能自己被当作输入参数一部分,Flask 提供了一些便利,用于从请求路径中提取变量。详情请参考 Flask 快速上手指南Werkzeug URL 路由模块文档

  • 请求的 QUERY_STRING 部分,能够经过 request.args 得到。

  • 请求以表单形式提交的数据(即以 application/x-www-form-urlencoded MIME Type 提交的报文),能够经过 request.form 得到。

  • request.argsrequest.formMultiDict 的实例,一般能够当作普通的字典使用,不过能够用于处理同名属性出现屡次的问题。

  • request 还提供了其它属性提供开发人员可能感兴趣的输入信息,包括 request.datarequest.headersrequest.filesrequest.cookies 等。

Flask 并未提供对数据的验证。这是程序员须要也必需自行处理的,由于来自网络的数据是不安全的。一般有以下作法:

  • 从路由规则提取的来自请求路径的信息,通常来讲比较简单,手写简单的验证便可。

  • 对于 request.argsrequest.form,简单但强大的验证数据的方法是经过 Flask-WTF 扩展,得益于 WTForms 库提供的验证机制,能够灵活的进行数据验证。

  • 其它输入来源获得的数据,例如 request.data 获得的 JSON 文本或 XML 文本,或者手工处理,或者借助注入 JSNON Schema 或 XML Schema 等技术进行验证。

结束响应和生成输出数据

当处理输入的过程成功得以执行,或者在处理数据的过程当中产生了没法继续处理的错误,咱们须要结束响应过程,并根据状况生成输出数据。

虽然大多数状况下,咱们响应请求输出数据的时候,只须要生成报文便可,可是咱们须要知道,Web 响应包含三个部分:状态码报头报文。Flask 提供了必要的手段让咱们能够指定状态码并在报头注入必要的信息。

从 HTTP 的角度看,响应有三种类型:

  • 具备 HTTP 2xx 状态码,从 HTTP 的角度这是成功的响应。

  • 具备 HTTP 3xx 状态码,从 HTTP 的角度这是特殊的用于跳转到新的 URL 地址,须要浏览器或者客户端从新发起请求的响应。

  • 具备 HTTP 4xx 或 5xx 状态码,从 HTTP 的角度这返回了错误信息,4xx 系列通常来讲是请求存在问题,5xx 系列通常来讲是服务器存在问题。

从输出的内容形态来看,一般有:

  • 适合人们阅读的内容,一般是 HTML,经过浏览器进行渲染。通常来讲,会须要经过 render_template() 函数结合模板文件的处理结果进行渲染,当内容较为简单时,直接以字符串形式生成 HTML 也是可行的。

  • 适合机器阅读的内容,经常使用于接口服务的开发,例如经过调用 jsonify() 函数返回 JSON 格式的数据。

  • 无内容。特定的状态码如 204 No Content 不得包含响应内容。这一般也是用于接口服务的开发。

在结束响应方面,存在两种方式:

  • 天然的方式,即请求处理函数以 return 语句返回必要的信息,能够是一个 flask.Response 类的实例,也能够是用于构建这样实例的参数。

  • 中途跳出的方式,即以抛出异常(通常来讲是对应了不一样状态码的特定的异常)的方式(包括调用 Flask 提供的 abort() 函数),来提前结束处理。

能够看到,Flask 在结束响应和生成输出方面考虑了很多状况,提供了许多的便利。然而,仍然存在一处缺失:就是当咱们以特定的异常或调用 abort() 函数跳出处理时,它假定错误信息是以 HTML 的方式提供的,当咱们遵循特定的约定开发接口服务器时,须要继承特定的异常重写必要的报头和报文。

微框架之“框架”

虽然许多时候以及 Flask 的许多部分咱们都是当作库来使用的,然而 Flask 毕竟是一个框架。简单来讲,这意味着代码的组织必然受到框架内在特色的影响。

虽然从 0.7 版开始,Flask 就支持了基于类形态的视图机制,可是笔者更倾向于以函数的方式编写视图,也就是采用更传统的过程式形态。这让代码保持简单,易于阅读和维护。

使用修饰器改变响应处理行为

使用类的形态带来的好处是能够开发人员编写一个或者多个视图类,它们具备必定的行为,再供给它们的子类使用。这些行为主要的做用是在执行真正的响应代码以前和/或以后进行必要的处理。若是缺乏必要的手段,采用过程形态编写程序,这样的通用代码若是分散在各个视图函数中,就显得冗余了。

幸运的是,得益于 Python 的修饰器机制,咱们一样能够简单的在视图函数得以执行以前并在执行以后,进行必要的预处理和/或后期处理,而无需编写冗余的代码。

举个例子,对于要求用户已经登陆方可执行的响应处理,若是以类形态编程,可能会表现为一个父类,在真正分发请求之间检查用户是否已经登陆,子类中就无需再考虑相应的问题,用户未登陆时请求不会被分发到相应的方法上。

以过程式形态来编写,将这样的判断写到每个函数里,显然是冗余的。所以,诸如扩展 Flask-Login 是经过提供名为 login_required 的修饰器来进行的,代码看起来相似:

@app.route('/path/to/here')
@login_required
def perform(**kwarg):
    params = fetch_params(request, kwargs)
    result = deal_with(params)
    return create_response(result)

这样,要求用户已经登陆的行为从代码上看简化为一行修饰器的使用,因为它与函数定义的位置至关近,实际上代码的可读性是更高的,阅读这段代码时,脑海中更容易强化要求用户已经登陆的约定。

对于今天较先进的程序设计语言提供的修饰器(Decorator)或者标注(Annotation)这样的技术,笔者的理解是这是一种容许开发人员定制的超级语法糖,它可以在编译时(对编译型语言)或模块导入时(对于解释型语言)或多或少的修改源代码。好的实现,可让源代码具备很高的易读性和可维护性的同时,具有更强大的功能。

在 Python 中,修饰器是一个便利的拦截和修改调用参数和返回值的机制。所以要深刻驾驭 Flask,了解如何编写修饰器是有必要的。

使用 Blueprint 切分视图模块

以类形态组织代码,自然具备良好的模块隔离。以过程式形态,咱们也能够将逻辑上相关的响应处理函数整理归并到不一样的 Python 模块中。更进一步,咱们还可使用 Flask 提供的 Blueprint 概念,Blueprint 在 API 与全局的 Flask 应用程序相似,使用 Blueprint 在实践中还具备一些额外的好处,例如能够较为方便的调整一个 Blueprint 中全部响应处理函数的路由规则,以及避免对全局的 Flask 对象循环依赖等。

保持面向对象的设计思想

即使是过程式形态组织代码,咱们总体的思路依然是面向对象的。每一个模块中的响应处理函数能够认为是当前运行中的 Flask 应用程序实例的方法。咱们能够经过 flask.current_app 对象得到应用程序上下文中的这个实例。此外,能够经过 flask.g 来存储实例属性。经过这样的思考,咱们仍然能够保持面向对象的程序设计思想。

使用回调和信号机制

Flask 具备请求上下文的概念,提供了必要的回调机制,回调有三类:

  • before_request 回调,当收到请求时被调用,用于请求数据的预处理,必要时也能够返回响应对象以抑制响应处理函数的执行。

  • after_request 回调,当请求处理正常结束时被调用(若是处理过程抛出了未被处理的异常则不会被调用),用于响应结果的后处理。它们接受一个响应对象做为参数,并应该返回一个响应对象。

  • teardown_request 回调,在每一个请求处理结束时被调用,一般用于清理或者回收资源。

能够看到 before_request 回调和 after_request 回调也能够用于请求处理函数的预处理和后处理。teardown_request 回调的使用场景较为特殊,以后咱们会更进一步说明。

每一类回调都可以设置多个,调用时,before_request 回调按照注册顺序进行,after_request 回调和 teardown_request 回调按照注册的逆序执行。

除了回调以外,Flask 也提供了更加灵活的信号机制,除了可以处理的时机比回调更多,也容许开发人员自定义信号。

WSGI 应用程序

Flask 是遵循 WSGI 规范的应用程序实现。WSGI 是许多 Python Web 框架——包括著名的 Django 框架——都共同遵循的规范。这意味着,基于 Flask 编写的应用程序,能够运行在不一样的 WSGI 服务器实现上,而且能够结合各类 WSGI 中间件使用。

“全局”变量

许多 WSGI 服务器的实现,例如 uWSGIGunicorn 均支持多种不一样的并发实现:多进程、多线程以及协程等。特别的,这些进程、线程或者协程一般会在多个请求中重复使用,所以,全局变量的设置须要尤其注意,它们在不一样的请求中应该是被隔离的。

Flask 提供的“全局”变量,如 requestsession 以及供开发人员使用的 g,在同一个请求的处理过程当中是“全局”的,而 Flask(借助底层的 Werkzeug)当心的处理这些“全局”信息,不会侵入其它的请求中,开发人员无需在乎使用的是何种并发技术以及并发部件是否被重复使用。

简单来讲,当开发人员须要设置在一个请求过程当中共享的全局变量时,应该将这些变量做为 g 的属性,Flask 知道应该如何处理它们。

此前将全局这个词打上引号,是由于更确切的说,这些变量是 Werkzeug 所谓的上下文局部变量,Werkzeug 文档中有专门的论述,值得阅读。

teardown_request 回调

以前咱们提到,Flask 只专一与获取请求和生成响应,这两个环节中间的业务逻辑,开发人员有彻底的自主。当咱们进行业务逻辑的开发时,可能会在请求开始申请一些资源,当请求结束时,不管成功或者失败,咱们都须要释放这些资源,这时候,咱们就须要使用 teardown_request 回调了。

此外,若是不是针对请求上下文,而是针对应用程序上下文结束时进行的清理操做,可使用 teardown_appcontext 回调。

相关文章
相关标签/搜索