深入理解 Flask 完整请求生命周期(附源码级分析)
作者 :Flask 实战笔记
日期 :2026-06-09
标签 :
FlaskPythonWeb开发后端WSGI阅读时长:约 18 分钟
前言
很多同学用 Flask 写了不少项目,但对于"一个 HTTP 请求从进入 Flask 到返回响应,中间到底经历了什么"这个问题,却说不清楚。
本文将从 WSGI 入口 到 响应返回,按领域逐层拆解 Flask 的完整请求生命周期,并补充官方文档和大多数教程刻意略过的细节:
- Application Context 和 Request Context 的区别与顺序
- 多个
before_request/after_request的处理顺序(含 Blueprint 级别) after_request与teardown_request的执行时机差异- Flask 信号系统(Signal)的使用场景
- 源码级分析:Flask 如何驱动整条生命周期
目录
- 一、生命周期总览
- [二、传输层:WSGI 入口与中间件](#二、传输层:WSGI 入口与中间件)
- 三、上下文层:双上下文机制
- [四、路由层:URL 匹配](#四、路由层:URL 匹配)
- [五、钩子层:多个 before / after 的执行顺序](#五、钩子层:多个 before / after 的执行顺序)
- 六、业务层:视图函数与响应生成
- 七、清理层:资源释放与上下文弹出
- [八、扩展:信号系统 Signal](#八、扩展:信号系统 Signal)
- 九、生产中最常踩的坑
- 十、总结
一、生命周期总览
一次 HTTP 请求经过以下层次,先看全局再逐层展开:
text
客户端发起请求
|
v
+---------------------+
| 传输层 | WSGI 服务器 + 中间件(Gunicorn / ProxyFix...)
+---------------------+
|
v
+---------------------+
| 上下文层 | push AppContext → push RequestContext
+---------------------+
|
v
+---------------------+
| 路由层 | Werkzeug URL Map 匹配 → 定位 View Function
+---------------------+
|
v
+---------------------+
| 钩子层 · 前置 | before_request(可短路,详见第五节)
+---------------------+
|
v
+---------------------+
| 业务层 | View Function → make_response()
+---------------------+
|
v
+---------------------+
| 钩子层 · 后置 | after_request(逆序执行,详见第五节)
+---------------------+
|
v
响应写回客户端(session 在此时写入 Cookie)
|
v
+---------------------+
| 清理层 | teardown_request → pop RequestContext
| | teardown_appcontext → pop AppContext
+---------------------+
核心规律:Flask 生命周期是一个洋葱模型------上下文、钩子层层包裹,视图函数在最中心;请求进来时从外到内,清理时从内到外。
二、传输层:WSGI 入口与中间件
2.1 WSGI 本质
Flask 本质是一个 WSGI Application ,所有 HTTP 请求的真实入口是 wsgi_app。以下是 Flask 源码(flask/app.py)的核心逻辑:
python
# flask/app.py Flask.wsgi_app(略去类型注解)
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # 构建请求上下文(尚未推入)
error = None
try:
try:
ctx.push() # 推入上下文(含 AppContext)
response = self.full_dispatch_request() # 路由 + 钩子 + 视图
except Exception as e:
error = e
response = self.handle_exception(e) # 触发 errorhandler
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response) # 写回 WSGI
finally:
ctx.pop(error) # 清理层:弹出上下文
full_dispatch_request 是整条生命周期的驱动器:
python
# flask/app.py Flask.full_dispatch_request
def full_dispatch_request(self):
self._got_first_request = True
try:
request_started.send(self, _sync_wrapper=None) # 触发信号
rv = self.preprocess_request() # 执行所有 before_request
if rv is None:
rv = self.dispatch_request() # 执行 View Function
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv) # make_response + after_request
environ 是标准 WSGI 字典,包含请求的全部原始信息:
| 键名 | 含义 |
|---|---|
REQUEST_METHOD |
GET / POST / PUT 等 |
PATH_INFO |
请求路径 /user/1 |
QUERY_STRING |
查询字符串 ?page=1 |
HTTP_* |
所有 HTTP 请求头 |
wsgi.input |
请求体(文件对象) |
2.2 WSGI 中间件
中间件在 Flask 接管之前介入,对 environ 进行修改。生产环境中最常用的是 ProxyFix,用于修正反向代理后的真实 IP 和协议:
python
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
# ProxyFix 修改 environ 后,才调用原始 Flask.wsgi_app
# 因此 request.remote_addr 已是真实客户端 IP,而非代理 IP
三、上下文层:双上下文机制
这是 Flask 设计的核心,也是最容易混淆的地方。大多数教程只提"请求上下文",实际上 Flask 有两个独立的上下文。
3.1 两个上下文对比
| 属性 | Application Context | Request Context |
|---|---|---|
| 代理对象 | current_app、g |
request、session |
| 生命周期 | 请求开始到结束(也可手动创建) | 仅限单次请求 |
| 典型用途 | 数据库连接、配置访问 | 请求参数、Cookie |
| 推入时机 | 先于 Request Context | 后于 App Context |
| 弹出时机 | 后于 Request Context | 先于 App Context |
3.2 推入与弹出顺序
ctx.push() 内部的逻辑(flask/ctx.py):
python
# flask/ctx.py RequestContext.push(核心逻辑)
def push(self) -> None:
# 若当前没有 AppContext,自动推入一个
app_ctx = _cv_app.get(None)
if app_ctx is None or app_ctx.app is not self.app:
app_ctx = self.app.app_context()
app_ctx.push() # 先推 AppContext
self._cv_app_tokens.append(...)
self._cv_tokens.append(
_cv_tokens.set(self) # 再推 RequestContext
)
...
text
请求开始:push AppContext → push RequestContext
请求结束:pop RequestContext → pop AppContext
3.3 典型错误:在 AppContext 清理时访问 request
python
@app.teardown_appcontext
def close_db(exception):
db = g.pop('db', None) # 正确:g 属于 AppContext,此时仍可用
if db is not None:
db.close()
# 错误!此时 RequestContext 已弹出,request 不可访问
# print(request.method) → RuntimeError: Working outside of request context
3.4 g 对象的生命周期
g 挂在 Application Context 上,但每次请求都是全新的 g,不能跨请求传递数据:
python
@app.before_request
def load_user():
# 将当前用户存入 g,仅在本次请求内有效
g.current_user = get_user_from_token(request.headers.get('Authorization'))
@app.route('/profile')
def profile():
return jsonify({'user': g.current_user.name}) # 同一请求内可用
四、路由层:URL 匹配
Flask 将路由委托给 Werkzeug 的 Map 和 Rule 系统。dispatch_request 内部逻辑:
python
# flask/app.py Flask.dispatch_request
def dispatch_request(self) -> ft.ResponseReturnValue:
req = request_ctx.request
app_exc = None
try:
rule = req.url_rule
# 将路径参数转换为 Python 类型(如 <int:id> → int)
return self.ensure_sync(
self.view_functions[rule.endpoint]
)(**req.view_args)
except Exception as e:
app_exc = e
raise
路由匹配发生在 request_context() 构建时(ctx.push() 之前),通过 Werkzeug 的 Map.bind 完成:
python
# URL 匹配等价逻辑
adapter = app.url_map.bind_to_environ(environ)
endpoint, view_args = adapter.match() # 失败时直接抛出 HTTPException
4.1 路由失败的两种情况
| 失败原因 | 抛出异常 | HTTP 状态码 |
|---|---|---|
| 路径不存在 | NotFound |
404 |
| 路径存在但 HTTP 方法不匹配 | MethodNotAllowed |
405 |
python
@app.route('/user/<int:id>', methods=['GET'])
def get_user(id):
...
# GET /user/1 → 200
# POST /user/1 → 405 MethodNotAllowed(不是 404!)
# GET /user/abc → 404 NotFound(int 转换失败)
4.2 路由转换器类型
python
@app.route('/post/<int:id>') # 整数
@app.route('/file/<path:filename>') # 含斜杠的路径
@app.route('/tag/<string:name>') # 字符串(默认)
@app.route('/uid/<uuid:uid>') # UUID
五、钩子层:多个 before / after 的执行顺序
这是整个生命周期中最重要、最容易踩坑的部分。
5.1 完整钩子管道流程图
下图展示了注册了多个钩子时的完整执行顺序,右侧标注了每个位置的典型应用:
text
请求进入钩子管道
|
v
+------------------------------------------+ 典型用途
| app.before_request #1(最先注册) | 请求日志记录、生成 request_id
+------------------------------------------+
| 若返回非 None → 短路 ──────────────────────────────────────┐
v |
+------------------------------------------+ |
| app.before_request #2(其次注册) | 身份鉴权、解析 JWT |
+------------------------------------------+ |
| 若返回非 None → 短路 ──────────────────────────────────────┤
v |
+------------------------------------------+ |
| bp.before_request(Blueprint 级,最后) | Blueprint 权限检查 |
+------------------------------------------+ |
| 若返回非 None → 短路 ──────────────────────────────────────┤
v |
+==========================================+ |
| View Function | your_code() |
| 业务逻辑 | |
+==========================================+ |
| |
v |
+------------------------------------------+ |
| make_response() | 返回值转为 Response |
+------------------------------------------+ |
| ◄──────────────────┘
v
+------------------------------------------+
| bp.after_request(Blueprint 级,最先执行)| 响应格式化、数据脱敏
+------------------------------------------+
|
v
+------------------------------------------+
| app.after_request #2(后注册先执行,逆序)| 安全响应头注入
+------------------------------------------+
|
v
+------------------------------------------+
| app.after_request #1(先注册后执行,逆序)| CORS 头、响应压缩
+------------------------------------------+
|
v
响应写回客户端(session 在此时写入 Cookie)
|
v
+------------------------------------------+
| teardown_request(无论异常必定执行) | 释放 DB 连接、记录耗时
+------------------------------------------+
三条核心规律:
before_request:按注册顺序 执行,app 级先于 Blueprint 级。任意一个返回非None则短路。after_request:按注册顺序的逆序执行,Blueprint 级先于 app 级。teardown_request:不属于请求/响应管道,无论是否发生异常、是否短路,必定执行。
5.2 源码验证:before_request 的执行逻辑
python
# flask/app.py Flask.preprocess_request
def preprocess_request(self):
# names 顺序:先 None(app 级),再当前 Blueprint 名称
names = (None, *reversed(request.blueprints))
for name in names:
if name in self.before_request_funcs:
for func in self.before_request_funcs[name]:
rv = self.ensure_sync(func)()
if rv is not None:
return rv # 短路:直接返回,后续钩子和 View 都不执行
return None
5.3 源码验证:after_request 的执行逻辑
python
# flask/app.py Flask.process_response
def process_response(self, response):
# names 顺序:先 Blueprint 名称,再 None(app 级)------与 before 相反
names = (*request.blueprints, None)
for name in names:
if name in self.after_request_funcs:
# reversed:同一作用域内,后注册的先执行
for func in reversed(self.after_request_funcs[name]):
response = self.ensure_sync(func)(response)
session_interface.save_session(self, session, response) # session 写入 Cookie
return response
5.4 多个 before_request 注册示例
python
api_bp = Blueprint('api', __name__, url_prefix='/api')
# --- before_request:注册顺序 = 执行顺序 ---
@app.before_request
def log_request(): # 第 1 个执行
g.start_time = time.time()
app.logger.info(f"→ {request.method} {request.path}")
@app.before_request
def authenticate(): # 第 2 个执行
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Unauthorized'}), 401 # 短路,后续全跳过
g.current_user = verify_token(token)
@api_bp.before_request
def check_permission(): # 第 3 个执行(Blueprint 级,最后)
if not g.current_user.has_permission('api'):
return jsonify({'error': 'Forbidden'}), 403
# --- after_request:注册顺序的逆序执行 ---
@app.after_request
def add_cors(response): # 注册第 1 个,执行第 3 个(最后)
response.headers['Access-Control-Allow-Origin'] = '*'
return response
@app.after_request
def add_security_headers(response): # 注册第 2 个,执行第 2 个
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
return response
@api_bp.after_request
def format_response(response): # Blueprint 级,执行第 1 个(最先)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response
# --- teardown_request:无论发生什么,必定执行 ---
@app.teardown_request
def log_and_cleanup(exception):
duration = time.time() - g.get('start_time', time.time())
app.logger.info(f"← {response.status_code} ({duration:.3f}s)")
db.session.remove() # 释放数据库连接
5.5 短路时的执行情况
text
正常请求:
before #1 → before #2 → bp.before → your_code() → bp.after → after #2 → after #1
before #2 触发短路(如鉴权失败):
before #1 → before #2 [返回 401] → bp.after → after #2 → after #1
(View Function 和 bp.before 被跳过;after_request 依然执行)
异常被 errorhandler 捕获:
before #1 → before #2 → bp.before → your_code() [抛异常]
→ errorhandler 处理
→ after_request 不执行
→ teardown_request 执行
5.6 errorhandler 触发时各钩子的执行情况
| 钩子 | 正常请求 | before 短路 | errorhandler 触发 |
|---|---|---|---|
before_request |
执行 | 部分执行 | 执行 |
your_code() |
执行 | 跳过 | 跳过 |
after_request |
执行 | 执行 | 不执行 |
teardown_request |
执行 | 执行 | 执行 |
六、业务层:视图函数与响应生成
6.1 make_response 自动转换
View Function 的返回值不必是 Response 对象,finalize_request 通过 make_response() 自动转换:
python
# flask/app.py Flask.finalize_request
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv) # 统一转换为 Response 对象
try:
response = self.process_response(response) # 执行 after_request
request_finished.send(self, response=response, _sync_wrapper=None)
except Exception:
if not from_error_handler:
raise
...
return response
常见返回值的转换规则:
python
return "Hello World" # Response("Hello World", 200, text/html)
return "Created", 201 # Response("Created", 201)
return {"key": "value"} # jsonify 转换(Flask 2.2+)
return jsonify({"key": "value"}), 200 # 显式 JSON 响应
return redirect(url_for('index')) # 302 重定向
return render_template('index.html', **ctx) # 渲染后的 HTML
return make_response("body", 200, {"X-Custom": "val"}) # 完全自定义
6.2 在 after_request 中修改响应
python
@app.after_request
def add_security_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-Content-Type-Options'] = 'nosniff'
return response # 必须 return,否则响应变为 None,触发 500
6.3 errorhandler 注册
python
@app.errorhandler(404)
def not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(Exception)
def handle_exception(e):
if isinstance(e, HTTPException):
return e
app.logger.exception(e)
return jsonify({'error': 'Internal Server Error'}), 500
七、清理层:资源释放与上下文弹出
7.1 teardown_request
teardown_request 无论请求是否发生异常都会执行,是释放请求级资源的正确位置。源码中由 ctx.pop() 触发:
python
# flask/ctx.py RequestContext.pop(核心逻辑)
def pop(self, exc=_sentinel):
...
rv = _cv_tokens.pop()
app.do_teardown_request(exc) # 触发所有 teardown_request
request_tearing_down.send(app, exc=exc)
...
if app_ctx is not None:
app_ctx.pop(exc) # 触发 teardown_appcontext
python
@app.teardown_request
def shutdown_db_session(exception):
if exception:
db.session.rollback()
app.logger.error(f"Request failed: {exception}")
db.session.remove() # 无论如何都释放连接
7.2 teardown_appcontext
在 teardown_request 之后、Application Context 弹出之前执行:
python
@app.teardown_appcontext
def close_connection(exception):
conn = g.pop('conn', None)
if conn is not None:
conn.close()
7.3 Session 写入时机
session 的序列化和 Cookie 写入由 process_response(after_request 阶段末尾)完成,在 teardown_request 之前,因此在 teardown 中修改 session 无效:
python
@app.teardown_request
def bad_session_write(exception):
session['key'] = 'value' # 无效:session 已在 after_request 阶段写入 Cookie
八、扩展:信号系统 Signal
Flask 内置了基于 Blinker 的信号系统,允许在生命周期各节点插入观察者,但不影响请求流程。
使用前需确保安装了 Blinker:
bash
pip install blinker
8.1 完整信号列表
Flask 提供以下内置信号,按生命周期顺序排列:
| 信号名 | 所在模块 | 触发时机 | 回调参数 |
|---|---|---|---|
appcontext_pushed |
flask.appcontext_pushed |
AppContext 推入后 | sender=app |
request_started |
flask.request_started |
RequestContext 推入后,before_request 前 | sender=app |
before_render_template |
flask.before_render_template |
模板渲染前(render_template 调用时) | sender=app, template=template, context=context |
template_rendered |
flask.template_rendered |
模板渲染完成后 | sender=app, template=template, context=context |
got_request_exception |
flask.got_request_exception |
异常被 handle_exception 处理前 | sender=app, exception=e |
request_finished |
flask.request_finished |
响应已生成,after_request 执行后 | sender=app, response=response |
request_tearing_down |
flask.request_tearing_down |
teardown_request 执行时 | sender=app, exc=exception |
appcontext_tearing_down |
flask.appcontext_tearing_down |
teardown_appcontext 执行时 | sender=app, exc=exception |
appcontext_popped |
flask.appcontext_popped |
AppContext 弹出后 | sender=app |
message_flashed |
flask.message_flashed |
flash() 被调用时 | sender=app, message=message, category=category |
8.2 信号与生命周期的对应位置
text
push AppContext
|
v
[appcontext_pushed]
|
push RequestContext
|
v
[request_started] ← before_request 前
|
before_request → View Function → after_request
| |
| (若调用 render_template)
| |
| [before_render_template]
| |
| [template_rendered]
|
v
[request_finished] ← after_request 后,响应写回前
|
响应写回客户端
|
v
[request_tearing_down] ← teardown_request 时
|
pop RequestContext
|
v
[appcontext_tearing_down] ← teardown_appcontext 时
|
[appcontext_popped]
|
pop AppContext
(异常路径)
View Function 抛出异常
|
v
[got_request_exception] ← errorhandler 处理前
|
errorhandler 处理 → finalize_request
8.3 案例一:请求耗时监控(APM)
python
import time
from flask import request_started, request_finished, g, request
@request_started.connect_via(app)
def on_request_started(sender, **kwargs):
g.request_start_time = time.time()
@request_finished.connect_via(app)
def on_request_finished(sender, response, **kwargs):
duration = time.time() - g.request_start_time
# 记录慢请求
if duration > 1.0:
app.logger.warning(
f"Slow request: {request.method} {request.path} "
f"took {duration:.3f}s, status={response.status_code}"
)
# 写入 Prometheus / StatsD
metrics.histogram('http_request_duration_seconds', duration, tags={
'method': request.method,
'endpoint': request.endpoint,
'status': response.status_code
})
8.4 案例二:异常上报(Sentry 集成)
python
from flask import got_request_exception
import sentry_sdk
@got_request_exception.connect_via(app)
def capture_exception(sender, exception, **kwargs):
# 在 errorhandler 处理之前捕获,保留原始调用栈
sentry_sdk.capture_exception(exception)
8.5 案例三:模板渲染审计
python
from flask import template_rendered
@template_rendered.connect_via(app)
def on_template_rendered(sender, template, context, **kwargs):
# 开发环境下打印模板渲染信息,排查渲染问题
app.logger.debug(
f"Template rendered: {template.name}, "
f"context keys: {list(context.keys())}"
)
8.6 案例四:Flash 消息审计
python
from flask import message_flashed
@message_flashed.connect_via(app)
def on_flash(sender, message, category, **kwargs):
# 记录所有 flash 消息,方便排查用户反馈
app.logger.info(f"Flash [{category}]: {message}")
8.7 案例五:请求生命周期调试(开发环境)
python
from flask import (
request_started, request_finished,
request_tearing_down, appcontext_tearing_down, request
)
if app.debug:
@request_started.connect_via(app)
def debug_request_started(sender, **kwargs):
print(f"[Signal] request_started: {request.method} {request.path}")
@request_finished.connect_via(app)
def debug_request_finished(sender, response, **kwargs):
print(f"[Signal] request_finished: status={response.status_code}")
@request_tearing_down.connect_via(app)
def debug_tearing_down(sender, exc, **kwargs):
print(f"[Signal] request_tearing_down: exception={exc}")
@appcontext_tearing_down.connect_via(app)
def debug_appcontext_tearing_down(sender, exc, **kwargs):
print(f"[Signal] appcontext_tearing_down: exception={exc}")
8.8 案例六:自定义信号
除了内置信号,你也可以创建自定义信号用于应用内部解耦:
python
from blinker import Namespace
# 定义自定义信号
app_signals = Namespace()
order_created = app_signals.signal('order-created')
user_registered = app_signals.signal('user-registered')
# 发送信号(在业务逻辑中)
@app.route('/orders', methods=['POST'])
def create_order():
order = Order.create(request.json)
db.session.commit()
# 触发信号,解耦后续逻辑
order_created.send(app, order=order, user=g.current_user)
return jsonify(order.to_dict()), 201
# 订阅信号(独立模块)
@order_created.connect_via(app)
def send_order_email(sender, order, user, **kwargs):
"""发送订单确认邮件"""
mail.send(to=user.email, subject=f"订单 #{order.id} 确认", ...)
@order_created.connect_via(app)
def notify_warehouse(sender, order, **kwargs):
"""通知仓库备货"""
warehouse_api.notify(order_id=order.id, items=order.items)
@order_created.connect_via(app)
def update_analytics(sender, order, **kwargs):
"""更新统计数据"""
analytics.track('order_created', amount=order.total)
8.9 信号 vs 钩子:核心区别
| 维度 | 钩子(Hook) | 信号(Signal) |
|---|---|---|
| 能否干预流程 | 可以(before_request 可短路) | 只读观察,不能干预 |
| 能否修改响应 | 可以(after_request) | 不能 |
| 注册方式 | @app.before_request 装饰器 |
signal.connect_via(app) |
| 多个订阅者 | 有严格顺序 | 无保证顺序,彼此独立 |
| 异常处理 | 异常直接中断流程 | 单个订阅者异常不影响其他订阅者 |
| 适用场景 | 鉴权、预处理、响应头注入 | 监控、日志、APM、业务解耦 |
| 跨应用 | 与 Flask 实例绑定 | 可以跨模块、跨扩展订阅 |
九、生产中最常踩的坑
坑 1:资源释放放错钩子
python
# 错误:after_request 在异常时不执行,数据库连接可能泄漏
@app.after_request
def release_db(response):
db.session.remove()
return response
# 正确:teardown_request 无论是否异常都执行
@app.teardown_request
def release_db(exception):
db.session.remove()
坑 2:误以为 g 是全局共享对象
python
# 错误:每次请求都是新的 g,跨请求数据会丢失
@app.before_request
def setup():
g.cache = {}
# 如需跨请求共享数据,使用 Redis / 数据库 / Flask-Caching
坑 3:Blueprint 钩子影响范围误解
python
# Blueprint 的 after_request 只对该 Blueprint 的路由生效
@auth_bp.after_request
def add_header(response):
response.headers['X-Auth'] = 'true'
return response
# 其他 Blueprint 的响应不会带这个 header
# 如需全局生效,注册在 app 上
@app.after_request
def add_header(response):
response.headers['X-Auth'] = 'true'
return response
坑 4:before_request 短路后 after_request 是否还执行
python
# 短路时:after_request 依然执行(因为 finalize_request 仍被调用)
# 但是:errorhandler 触发时,after_request 不执行
@app.before_request
def auth():
return jsonify({'error': 'Unauthorized'}), 401
# after_request 会执行(短路返回的也是正常响应流程)
# 对比:
@app.route('/crash')
def crash():
raise ValueError("unexpected error")
# errorhandler 处理后,after_request 不执行
# 请求日志等关键逻辑应放在 teardown_request
坑 5:after_request 忘记 return response
python
@app.after_request
def process(response):
response.headers['X-Custom'] = 'value'
# 忘记 return → 响应变为 None → 500 Internal Server Error
return response # 必须 return
十、总结
| 层次 | 核心组件 | 关键行为 |
|---|---|---|
| 传输层 | WSGI 服务器、中间件 | 中间件在 Flask 之前修改 environ |
| 上下文层 | AppContext、RequestContext | App 先推后弹,Request 后推先弹 |
| 路由层 | Werkzeug URL Map | 路径不存在→404,方法不匹配→405 |
| 钩子层 | before / after / teardown | before 顺序执行可短路;after 逆序执行,异常时不执行;teardown 必执行 |
| 业务层 | View Function、make_response | 返回值自动转换;session 在 after_request 末尾写入 Cookie |
| 清理层 | teardown_request/appcontext | 资源释放的正确位置;内到外的弹出顺序 |
参考资料
- Flask 官方文档 - Application Context
- Flask 官方文档 - Request Context
- Flask 官方文档 - Signals
- Flask 源码 - app.py
- Flask 源码 - ctx.py
- Werkzeug 路由文档
- PEP 3333 - WSGI 规范
如有错误,欢迎评论区指出。