第2章:应用的基本结构-Flask Web 开发(第2版)

本章将学习到 Flask 应用的不同部分,并将开发并运行第一个 Flask 应用。

初始化

所有 Flask 应用都必须创建一个应用(application)对象。Web 服务器会把客户端所有的请求,使用WSGI协议(Web Server Gateway Interface),通过对应的处理器发送给该对象。应用对象是 Flask 类的一个实例。使用下面的命令创建:

from flask import Flask
app = Flask(__name__)

Flask 类的构造函数所需的唯一参数是主模块或应用所在包的名字。大部分应用中,Python 的 __name__ 变量的值,是该参数所需的合法值。

很多 Flask 初学者都会很困惑:为什么要将 __name__ 传递给 Flask 类的构造函数?Flask 框架使用该参数来识别应用所在位置,以便通过该位置来定位应用的其它资源,比如:图片和模板等。

后面你将会学习到更多地、更复杂的初始化方式。但对于简单应用来说,上面的代码足够了。

路由(route)和视图函数(view functions)

Web 浏览器等客户端,将 请求(request) 发送给 Web 服务器,并转发给 Flask 的应用对象。应用对象需要知道每个 URL 请求要运行哪段代码,所以它对所有 URL 和对应的 Python 函数进行了映射。URL 和对应的处理函数,通过路由来关联。

定义路由最常用的方法是,使用 app.route 装饰器:

@app.route('/')
def index():
    return '<h1>Hello world</h1>'

上面的代码,将 index() 函数注册为应用的根URL(root URL, /)的处理器。

除了使用 @app.route 装饰器,Flask 还提供了一种传统方式来注册路由:app.add_url_rule() 方法:

def index():
    return '<h1>Hello world</h1>'
app.add_url_rule('/', 'index', index)

index() 这种处理器函数被称为视图函数

假设,Web 应用部署在一台与 www.example.com 域名关联的服务器上,使用浏览器访问 www.example.com/ 时,将会在服务器上触发运行 index() 函数。视图函数的返回值是客户端接收到的响应(response)。视图函数可以返回简单的文本、HTML,也可以返回复杂的表单。

如果你仔细观察每天使用的一些服务,你会发现它们的一些 URL 会包含可变部分。比如微博的 URL 格式为:https://weibo.com/u/<用户ID>/,它包含了用户ID,用来区分不同的用户。Flask 支持这种 URL,只需要在 @app.route 装饰器中使用特殊的语法。下面的例子定义了一个包含动态部分的路由:

@app.route('/user/<name>')
def user(name):
    return '<h1>Hello, {}!</h1>'.format(name)

路由中,包含在一对尖括号内的部分就是 URL 的动态部分。动态部分将作为参数传递给视图函数。

路由中的动态部分,默认是 string (字符串)类型,但它也可以是其它类型。比如 /user/<int:id> 路由将只匹配形如 /user/123 这种包含了整数的 URL。Flask 的路由支持 stringintfloatpath 等类型。

一个完整的应用

请在 flasky 目录下,创建一个 hello.py 文件,并输入以下内容:

# 示例 2.1 hello.py:一个完整的 Flask 应用
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World!</h1>'

hello.py 定义了一个应用对象和一个路由及其对应的视图函数。

如果你从 Github 克隆了示例代码,可以使用 git checkout 2a 签出本示例。

开发用的 Web 服务器

Flask 内置了一个开发用的 Web 服务器,使用 flask run 命令即可启动该服务器。这个命令会读取 FLASK_APP 环境变量的值,并查找包含了应用对象的,与该环境变量值同名的 Python 脚本。

要运行上例的 hello.py,请确保已激活虚拟环境,并安装了 Flask。然后,在 Linux/Mac OS 中,使用下面的命令来运行:

(venv) $ export FLASK_APP=hello.py
(venv) $ flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

在 Windows 中,使用下面的命令来运行:

(venv) $ set FLASK_APP=hello.py
(venv) $ flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Linux/Mac OS 和 Windows 的运行命令,唯一的区别之处在于如何设置环境变量。前者使用 export ,后者使用 set

服务一旦启动,它就会进入“死循环”:接收请求并提供服务。要停止服务,请按下 CTRL + C 组合键。

在服务运行的情况下,打开浏览器,输入 http://127.0.0.1:5000/http://localhost:5000/,你将会看到显示的结果。

这个开发用的 Web 服务器只是用来开发和测试 Flask 应用,不能用于生产环境。你将在第17章学习到如何在生产环境里部署 Flask 应用。

还可以在 Python 代码中,通过 app.run() 来启动开发用的 Web 服务器。在没有提供 flask run 命令的旧版 Flask 中,通常都使用这个方式来启动开发用的 Web 服务器。另一个用处是在第15章所介绍的单元测试里。

# 通过 app.run() 启动开发用的 Web 服务器

if __name__ == '__main__':
    app.run()

动态路由

这个应用的第2版,将加入第二个路由,这个路由是动态路由。访问这个 URL 的时候,需要给定动态路由所需要的动态数据。

# 示例 2-2 hello.py:带有动态路由的 Flask 应用
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello World!</h1>'

@app.route('/user/<name>')
def user(name):
    return '<h1>Hello, {}!</h1>'.format(name)

如果你从 Github 克隆了示例代码,可以使用 git checkout 2b 签出本示例。

要测试动态路由,请在开发用 Web 服务器运行的状态下,在浏览器中输入 http://localhost:5000/user/Dave。应用程序将使用 name 作为动态参数,给每个用户作出不同的响应内容。试着将 Dave 换成其它名字看看效果。

调试(debug)模式

Flask 调试模式一旦开启,开发用的 Web 服务器将默认启用两个模块:reloaderdebugger。前者用于,当代码修改时,自动重新加载并重启 Web 服务器;后者用于,当应用发生错误、异常时,在网页中显示详细的调试信息。

默认情况下,调试模式处于未开启状态。要开发调试模式,只需要在执行 flask run 命令之前,设置环境变量 FLASK_DEBUG 的值为 1

(venv) $ export FLASK_APP=hello.py
(venv) $ export FLASK_DEBUG=1
(venv) $ flask run
 * Serving Flask app "hello"
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 273-181-528

在 Windows 中,将上述命令中的 export 改为 set

如果是使用 app.run() 方法,FLASK_APPFLASK_DEBUG 环境变量不起作用。而是使用以下方法开启调试模式:app.run(debug=True)

警告:不要在生产环境中开启调试模式!

命令行选项

flask 命令支持多个选项。要查看这些选项,只需要在命令行中运行 flask --help 或直接运行不带任何参数的 flask 命令。

flask shell 命令用于启动一个当前应用上下文中的 Python Shell 会话。可以在这个会话中进行维护任务或测试、调试错误。

请求-响应环

应用程序上下文(application context)和请求上下文(request context)

当 Flask 从客户端接收到请求时,它需要为可以处理它的视图函数提供一些对象。一个很好的例子就是 request 对象。该对象包装了客户端发送过来的 HTTP 请求。

为了让视图函数可以访问到 request 对象,最显而易见方式是,Flask 将其作为参数传递给视图函数。但这样会使应用中的每个视图函数都带上额外的参数。如果视图函数所需要的不仅仅是 request 对象,还要其它对象时,情况会变的很复杂。

为了避免使用大量不总是需要的参数来混淆视图函数,Flask 使用 context(上下文) 来临时将某些对象提升为全局可访问。得益于上下文,视图函数可以写成这样:

from flask import request

@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '<p>Your browser is: {}</p>'.format(user_agent)

请注意,在这个视图函数内部,是如何将 request 对象当作全局变量来使用的。事实上,request 对象并不是全局变量。在多线程服务器中,多个线程可以同时处理不同客户端发来的不同请求,所以每个线程需要看到不同的 request 对象。上下文使 Flask 可以将特定的变量提升为全局可访问,并确保线程之间相互隔离。

Flask 中有两个上下文:应用程序上下文(application context)请求上下文(request context)。下表列示了每个上下文暴露的变量。

变量名 上下文 说明
current_app 应用程序上下文 当前应用程序的应用对象
g 应用程序上下文 应用程序在处理请求期间可用于临时存储的对象。这个对象在每次请求时都会被重置
request 请求上下文 包装了客户端发送过来的 HTTP 请求。
session 请求上下文 用户会话。应用程序用来“记住”多个请求之间需要共享的数据。它是一个字典

Flask 在向应用程序分发请求之前激活(activates)(或推送(pushes))应用程序上下文和请求上下文,并在处理请求后将其删除。当应用程序上下文被激活后(pushed),current_appg 变量在当前线程内可见;当请求上下文激活后,requestsession 变量在当前线程可见。如果在应用程序上下文或请求上下文之外,试图去访问这些变量,将引发错误。

下面的 Python Shell 会话演示了应用程序上下文如何工作:

>>> from hello import app
>>> from flask import current_app
>>> current_app.name
Traceback (most recent call last):
...
RuntimeError: working outside of application context
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()

上例演示了,在应用程序上下文之外获取 current_app.name 将会引发错误,而一旦应用程序上下文激活后,它正常工作。注意,例中使用 app.app_context() 来获取应用程序上下文。

分发请求

Flask 使用 URL 映射(URL map) 来维护 URL 和视图函数之间的关联关系。而这个映射通过 app.route 装饰器和 app.add_url_rule() 方法来提供数据。

下例演示了如何获取 hello.py 的 URL 映射。

>>> from hello import app
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
 <Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])

//user/<name> 路由是在 hello.py 中,通过 app.route 定义的。/static/<filename> 则是 Flask 为访问静态资源而定义的特殊路由。你将在第3章学习到有关静态资源的内容。

(HEAD, OPTIONS, GET) 显示的是路由接受的 HTTP 请求方法。HTTP 规范明确要求,每个 HTTP 请求都要有对应的请求方法,用来告诉服务器以何种行为来处理客户端请求。Flask 将请求方法附加到视图函数中,以便相同URL的不同请求方法,可以由相同或不同的视图函数作出不同的处理。HEADOPTIONS 方法由 Flask 自动管理。所以,这个例子中的三个路由,实际都附加了 GET 请求方法。你将在第4章学习到如何创建其它请求方法的路由。

request 对象

如你所见,Flask 通过上下文中的 request 变量将请求对象暴露出来。它是一个非常有用的对象,它包含了客户端通过 HTTP 请求发送到服务器的所有信息。

下表列示了 Flask 中 request 对象的常用属性和方法。

属性或方法 说明
form 字典。包含了所有通过 HTTP 请求提交的表单的字段
args 字典。包含了所有通过 URL 发送的参数字段
values 字典。包含了 formargs 的所有字段
cookies 字典。包含了 HTTP 请求中所有的 cookie
headers 字典。包含了 HTTP 请求中的所有 HTTP 请求头信息
files 字典。包含了 HTTP 请求中所有上传的文件
get_data() 获取 HTTP 请求体中的主体部分
get_json() 获取 HTTP 请求体中的 JSON 数据,并将其解析为 Python 的字典
blueprint 处理该请求的蓝图(Blueprint)的名称。蓝图将在第7章介绍
endpoint 处理该请求的端点(endpoint)的名称。Flask 使用路由对应的视图函数的名字作为端点的名称
method HTTP 请求的请求方法,比如 GETPOST
scheme URL 的 scheme。(httphttps ,可以理解为本次请求使用的传输协议)
is_secure() 如果是通过 HTTPS 传输,返回 True
host 主机名,包含端口
path URL 的路径部分
query_string URL 的查询字符串部分,原始的二进制值
full_path URL 的完整路径。包括 pathquery_string
url 客户端请求的完整 URL
base_url url 类似,但不包含 query_string 部分
remote_addr 客户端的 IP 地址
environ 字典。本次 HTTP 请求原始的 WSGI 环境变量

请求钩子(request hooks)

通常用于在请求前或请求后执行某些代码。比如,每个请求开始之前,连接数据库或对用户进行认证。请求钩子以装饰器的形式进行实现。下面是 Flask 支持的4种请求钩子:

  • before_request:注册一个在每次请求前都运行的函数
  • before_first_request:注册一个在第一次请求前运行的函数。它可以很方便的对一些服务进行初始化。
  • after_request:注册一个在每次请求后都运行的函数,那些引发未处理异常的除外
  • teardown_request:注册一个在每次请求后都运行的函数,包括那些引发未处理异常的

请求钩子和视图函数之间,通常使用 g 变量来共享数据。比如,before_request 可以从数据库中获取已登录用户的数据,并保存到 g.user 中,视图函数通过 g.user 来获取已登录用户的数据。

响应(response)

当 Flask 调用一个视图函数时,它期望视图函数返回一个响应值给请求。大部分时候,视图函数都简单的返回一个字符串,作为 HTML 页面发回给客户端。

但 HTTP 协议对响应的要求远非一个简单的字符串。HTTP 响应最重要的一个部分是状态码,Flask 默认返回 200 状态码。这个状态码表示该请求已成功响应。

当视图函数要返回一个非200的状态码时,可以在视图函数中返回第二个值。第二个值是一个整数,表示状态码。下例将返回一个400状态码,该状态码代表非法请求:

@app.route('/')
def index():
    return '<h1>Bad Request</h1>', 400

视图函数还可以返回第三个值。这个值是一个字典,表示附加到 HTTP 响应的额外的头部信息(HTTP Header)。

作为对视图函数返回一个、两个或三个值的替代,视图函数可以直接返回一个 response 对象。make_response() 函数接收1-3个参数,各参数的作用和之前讨论的视图函数返回的三个值的含义一样,并且返回一个等效的 response 对象。

下例演示了如何返回一个 response 对象,并在其中设置 cookie:

from flask import make_response
@app.route('/')
def index():
    response = make_response('<h1>This document carries a cookie!</h1>')
    response.set_cookie('answer', '42')
    return response

下表列示了 response 对象常用的属性和方法:

属性或方法 说明
status_code HTTP 状态码
headers 跟字典类似的一个对象。包含了 HTTP 响应中的所有头部信息
set_cookie() 在 HTTP 响应中添加一个 cookie
delete_cookie() 在 HTTP 响应中删除一个 cookie
content_length HTTP 响应主体部分的长度
content_type HTTP 响应主体部分的媒体类型
set_data() 将 HTTP 响应主体部分设置为一个字符串或二进制值
get_data() 获取 HTTP 响应主体部分

有一个特殊的 HTTP 响应,称为 redirect(重定向)。这种 HTTP 响应不包含任何页面文档,只是让浏览器导航到一个新的 URL。通常在 Web 表单中使用重定向,你将在第4章学习到这部分内容。

重定向通常包含 302 状态码和一个用于 Location 头部信息的 URL。你可以手动构建重定向响应,但 Flask 提供了 redirect() 函数来快速构建该类型的响应:

from flask import redirect

@app.route('/')
def index():
    return redirect('http://www.example.com')

另一种特殊的 HTTP 响应是用于错误处理的 abort() 函数。下面的代码演示了,如果通过 id 这个动态参数没有获取到用户信息,将返回一个404错误。

from flask import abort

@app.route('/user/<id>')
def get_user(id):
    user = load_user(id)
    if user is None:
        abort(404)
    return '<h1>Hello, {}!</h1>'.format(user.name)

注意,abort() 执行完之后,函数代码块不会继续执行,因为它会抛出异常。

本文并非《Flask Web Development(2nd edition)》的全文翻译。而只是在阅读该书时,对自认为重点的内容进行记录以及思考。由于水平有限,本文所述内容难免出现不足或错误。请通过购买正版图书来进行更系统地学习,原书地址。本站将依照本系列文章录制视频教程,届时本文未作记录的部分以及项目实际开发中所需要的知识都会在视频教程中进行补充。

flask Flask Web Development(2nd edition) 2018-03-25 15:19 2034392