初始化
所有 Flask 程序都必须创建一个 app 实例。Web 服务器使用 Web 服务器网关接口协议(Web Server Gateway Interface, WSGI)把接收自客户端的所有请求都转交给这个对象处理。 app 实例是 Flask 类的对象:
from flask import Flask
app = Flask(__name__)
Flask 类的构造函数自有一个必须指定的参数, 即 app 主模块或包的名字。
Flask 用 name 这个参数决定程序的根目录, 以便稍后能够找到相对于程序根目录的资源文件位置。
路由和视图函数
客户端(例如 Web 浏览器)把请求发送给 Web 服务器, Web 服务器再把请求发送给 Flask app 实例。 app 实例需要知道对每个 URL 请求运行哪些代码, 所以 app 实例保存了一个 URL 到 Python 函数的映射关系。处理 URL 和函数之间关系的程序称为路由。
在 Flask 中使用 app 实例提供的 app.route
装饰器把所装饰的函数注册为路由:
@app.route('/')
def index():
return '<h1>Hello, 世界!</h1>'
装饰器是可以把函数注册为事件的处理程序。
前例是把 index() 函数注册为 app 根地址的处理程序。如果部署的程序的服务器域名为 www.example.com, 在浏览器中访问 http://www.example.com 后会触发服务器执行 index() 函数。这个函数的返回值称为 响应, 它是客户端接收到的内容。如果客户端是 Web 浏览器, 响应就是显示给用户看的文档。
像 index() 这样的函数称之为 视图函数(view function)。视图函数返回的响应可以是包含 HTML 的简单字符串,也可以是复杂的表单。
可变 URL:
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
路由中的动态部分默认使用字符串, 也可以使用 int/float/path 类型, path 类型也是字符串, 但不把斜线作为分割符, 而将其当作动态片段的一部分。
@app.route('/user/<int:id>')
启动服务器
app 实例使用 run
方法启动 Flask 集成的 Web 服务器:
if __name__ == '__main__':
app.run(debug=True)
__name__ == '__main__'
确保了只有直接 执行这个脚本时才启动 Web 服务器。如果这个脚本由其它脚本引入, 程序假定父级脚本会启动不同的服务器, 因此不会执行 app.run()
。
服务器启动后会进入轮询, 等待并处理请求, 轮询会一直运行,直到程序停止,例如按 Ctrl-C 键。
一个完整的 app
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello, 世界!</h1>'
@app.route('user/<name>')
def user(name):
return '<h1>hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)
启动这个 app:
(venv) $ python hello.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
在浏览器中键入:
http://localhost:5000/user/Dave
会显示:
<h1>Hello, Dave!</h1>
请求/响应循环
app 和请求上下文
Flask 从客户端收到请求时, 要让视图函数能访问一些对象, 这样才能处理请求。请求对象封装了客户端(例如浏览器)发送的 HTTP 请求。
要让视图函数能访问请求对象, 一个显而易见的方式是把请求对象作为参数传递给视图函数, 不过这会导致程序中每个视图函数都增加一个参数。如果视图函数还要访问其它对象, 那么视图函数会变得越来越臃肿和难以维护。
为此, Flask 使用 上下文 临时把某些对象变为全局可访问:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>你的浏览器是 %s</p>' % user_agent
在这个例子中我们把 request 当作全局变量来使用。事实上, request 不可能是全局变量, 你想想, 在多个线程同时处理不同客户端发送的不同请求时, 每个线程看到的 request 对象必然不同。 Flask 使用上下文让特定的变量在每一个线程中全局可访问, 与此同时却不会干扰其它线程。
多线程 Web 服务器会创建一个线程池, 再从线程池中选择一个线程用于处理接收到的请求。
在 Flask 中有两种上下文: app 上下文
和 请求上下文
。下表列出了这两种上下文提供的全局变量:
变量名 | 上下文 | 说明 |
---|---|---|
current_app | app上下文 | 当前所激活app的app实例 |
g | app上下文 | 处理请求时用作临时存储的对象。每次请求都会重设这个变量 |
request | 请求上下文 | 请求对象, 封装了客户端发出的 HTTP 请求中的内容 |
session | 请求上下文 | 用户会话, 用于存储请求之间需要"记住"的值的字典 |
Flask 在分发请求之前激活(或推送)app上下文和请求上下文, 请求处理完成后再将其删除。 app 上下文在被推送后, 就可以在线程中使用 current_app
和 g
变量。类似地, 在请求上下文被推送后, 就可以使用 request
和 session
变量。如果我们使用这些变量时没有激活 app 上下文或请求上下文, 那么程序就会出错。
激活虚拟环境后进入 Python shell, 下面演示app上下文的使用方法:
>>> from hello import app
>>> from flask import current_app
>>> current_app.name
...
RuntimeError: Working outside of application context.
>>> app_ctx = app.app_context()
>>> app_ctx.push() # 推送 app 上下文
>>> current_app.name
'hello'
>>> app_ctx.pop() # 弹出 app 上下文
在这个例子中, 没有激活 app 上下文之前就调用 current_app.name 就会导致错误, 但是推送完上下文之后就可以调用了。
注意, 在 app 实例上调用 .app_context() 方法便获得了一个程序上下文。
请求调度
程序收到客户端发来的请求时, 要找到处理该请求的视图函数。Flask 通过在 app 的 URL 映射中查找请求的 URL 来完成这个任务。 URL 映射是 URL 和视图函数之间的对应关系。 Flask 使用 app.route
装饰器/非装饰器形式的 app.add_url_rule()
生成映射。
我们在 Python shell 中查看 Flask 的 app 中映射是什么样子的:(所有操作请确保你已经激活了虚拟环境)
(venv) $ python
>>> from hello import app
>>> app.url_map
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
/
和 /usr/<name>
路由在 app 中使用 app.route
装饰器定义。 /static/<filename>
路由是 Flask 添加的特殊路由, 用于访问静态文件。
URL 映射中的 HEAD、OPTIONS、GET 是请求方法。Flask 为每个路由都指定了请求方法, 这样不同的请求发送到相同的 URL 上时, 会使用不同的视图函数进行处理。 HEAD 和 OPTIONS 方法由 Flask 自动处理, 因此可以说上面的 app 中 URL 映射中的 3 个路由都使用 GET 方法。
请求钩子
有时需要在请求之前或之后执行代码会很有用。例如, 在请求开始时我们可能需要创建数据库连接/认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码, Flask 提供了注册通用函数的功能, 注册的函数可在请求被分发到视图函数之前/之后被调用。
请求钩子 使用装饰器实现。 Flask 支持以下 4 种钩子:
- before_first_request: 注册一个函数, 在处理第一个请求之前运行。
- before_request: 注册一个函数, 在每次请求之前运行。
- after_request: 注册一个函数, 如果没有未处理的异常抛出, 则在每次请求之后运行。
- teardown_request: 注册一个函数, 即使有未处理的异常抛出, 也在每次请求之后运行。
在请求钩子和视图函数之间共享数据一般使用上下文全局变量 g
。 例如 before_request
处理程序可以从数据库中加载已登录用户, 并将其保存到 g.user
中。随后调用视图函数时, 视图函数再使用 g.user
获取用户。
响应
Flask 调用视图函数后, 会将其返回值作为响应的内容。大多数情况下, 响应就是一个简单的字符串, 作为 HTML 页面回送客户端。
在响应的文本之后添加一个状态码:
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
表示请求无效。
视图函数返回的响应还可以接收第三个参数, 这是一个由首部(header)组成的字典, 可以添加到 HTTP 响应中。
Flask 还可以返回 Response 对象。 make_response()
函数可接受 1/2/3 个参数(和视图函数的返回值一样), 并返回一个 Response 对象。
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
重定向是一种特殊的响应类型。这种响应类型没有页面文档, 只告诉浏览器一个新的地址用以加载新页面。Flask 已经提供了 redirect()
函数:
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
还有一种特殊的 abort
响应, 用于处理错误:
from flask import abort
@app.route('/user/<id>')
def get_user(id):
user = load_user(id):
if not user:
abort(404)
return '<h1>Hello, %s!</h1>' % user.name
Flask 扩展
使用Flask-Script支持命令行选项
Flask 的开发 Web 服务器支持很多启动设置选项,但只能在脚本中作为参数传给 app.run()
函数。这种方式并不十分方便,传递设置选项的理想方式是使用命令行参数。
Flask-Script 是一个 Flask 扩展,为 Flask 程序添加了一个命令行解析器。Flask-Script 自带了一组常用选项,而且还支持自定义命令。
# 安装
(venv) $ pip install flask-script
要使用 flask-script 需要在 hello.py 修改下程序:
from flask import Flask
from flask.ext.script import Manager
app = Flask(__name__)
manager = Manager(app)
@app.route('/')
def index():
return '<h1>Hello,World</h1>'
if __name__ == '__main__':
manager.run()
专为 Flask 开发的扩展都暴漏在 flask.ext 命名空间下。Flask-Script 输出了一个名为Manager 的类,可从 flask.ext.script 中引入。
这个扩展的初始化方法也适用于其他很多扩展:把 app 实例作为参数传给扩展的构造函数,初始化主类的实例。创建的对象可以在各个扩展中使用。在这里,服务器由 manager.run()
启动,启动后就能解析命令行了。
注意, 在 Python 3 中要这样导入 flask-script 扩展, from
flask_script import Manager
现在运行 hello.py,会显示一个帮助消息:
$ python hello.py
usage: hello.py [-h] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell 在 Flask 应用上下文中运行 Python shell
runserver 运行 Flask 开发服务器:app.run()
optional arguments:
-h, --help 显示帮助信息并退出
shell 命令用于在程序的上下文中启动 Python shell 会话。你可以使用这个会话中运行维护任务或测试,还可调试异常。顾名思义, runserver 命令用来启动 Web 服务器。运行 python hello.py runserver 将以调试模式启动 Web 服务器,但是我们还有很多选项可用:
$ python hello.py runserver --help
usage: hello.py runserver [-?] [-h HOST] [-p PORT] [--threaded]
[--processes PROCESSES] [--passthrough-errors] [-d]
[-D] [-r] [-R]
--host
参数是个很有用的选项,它告诉 Web 服务器在哪个网络接口上监听来自客户端的连接。默认情况下,Flask 开发 Web 服务器监听 localhost 上的连接,所以只接受来自服务器所在计算机发起的连接。下述命令让 Web 服务器监听公共网络接口上的连接,允许同网中的其他计算机连接服务器:
(venv) $ python hello.py runserver --host 0.0.0.0
* Running on http://0.0.0.0:5000/
* Restarting with reloader
现在,Web 服务器可使用 http://a.b.c.d:5000/
网络中的任一台电脑进行访问,其中 “a.b.c.d” 是服务器所在计算机的外网 IP 地址。