(五)请求上下文、应用上下文与资源收尾
1. 当前请求与当前应用分别保存信息
路由中能写 request.args,是因为 Flask 在处理请求时建立了请求上下文;应用配置和资源则通过应用上下文取得。先观察能在不同位置读取什么。
# 项目文件:demos/context_trace.py
# 分别进入应用上下文和请求上下文,观察代理对象在上下文退出后的可用状态。
from flask import Flask, current_app, g, request
app = Flask(__name__)
try:
print(request.path)
except RuntimeError:
print("请求之外,没有 request.path")
with app.app_context():
g.label = "本次命令的资源"
print("应用上下文", current_app.name, g.label)
try:
print(request.path)
except RuntimeError:
print("只有应用上下文,仍然没有 HTTP 请求")
with app.test_request_context("/books?finished=0"):
print("请求上下文", request.path, request.args["finished"])
print("新上下文是否沿用 label", hasattr(g, "label"))应用上下文让 current_app 和 g 可用,单独创建它不会凭空产生一条 HTTP 请求。test_request_context 则准备请求数据,也同时准备所需的应用上下文。这个方法只准备上下文,不会自动执行路由和 before_request;需要经过完整分发时,用测试客户端发送请求。
g 是应用上下文中的临时存放处。通常一条请求会有自己的应用上下文,因此数据库连接放在 g.db 后可以在本次请求内复用,结束时清理。命令行也可以用 with app.app_context() 建立一次应用上下文,使用相同数据库函数。
前一章的 SQLAlchemy Session 是 ORM 工作对象;Flask 的 session 是当前浏览器会话的数据映射;g 又是这次应用上下文的临时数据。它们解决的问题不同,不能仅凭名字都像“会话”就混在一起。
2. 沿一次请求观察回调顺序
# 项目文件:demos/lifecycle_trace.py
from flask import Flask
app = Flask(__name__)
events = []
@app.before_request
def prepare():
events.append("before:准备")
@app.get("/")
def index():
events.append("view:处理")
return "书架"
@app.after_request
def decorate(response):
events.append("after:补响应头")
response.headers["X-Example"] = "flask"
return response # after_request 必须交回响应对象。
@app.teardown_request
def finish_request(_error):
events.append("teardown:请求收尾")
@app.teardown_appcontext
def finish_app(_error):
events.append("teardown:应用上下文收尾")
client = app.test_client()
print(client.get("/").headers["X-Example"])
print(events)事件依次为准备、视图处理、补响应头、请求收尾、应用上下文收尾。before_request 可以准备用户身份;返回响应时也可以提前结束这次分发。after_request 接收响应并交回响应,适合补头。teardown_appcontext 适合关闭保存在 g 中的连接,正常和异常路径都会执行。
数据库提交放在业务动作成功的位置,连接关闭放在收尾位置。被处理过的 HTTP 错误与异常对象之间的关系并不等于事务是否成功,因此本章不会靠收尾参数决定是否提交。