Flask 教程

Flask 请求生命周期

理解 Flask 请求生命周期(Request Lifecycle) 是掌握 Flask 高级特性的关键。它解释了从用户浏览器发出请求,到 Flask 处理完毕返回响应的完整过程。

这个过程可以清晰地分为 5 个阶段。我们将结合代码示例,看看数据是如何在这些阶段中流转的。


🔄 Flask 请求生命周期全景图

纯文本
plaintext
1. 接收请求 (WSGI)
      ↓
2. 推入上下文 (Context Push)
      ↓
3. 前置处理 (Before Request)
      ↓
4. 路由匹配与视图执行 (View Function)
      ↓
5. 后置处理 (After Request) & 清理 (Teardown)
      ↓
6. 返回响应 (Response)

第一阶段:接收请求 (WSGI Entry)

当用户在浏览器输入 URL 并回车时,HTTP 请求首先到达 Web 服务器(如 Nginx),然后转发给 WSGI 服务器(如 Gunicorn 或开发环境的 Werkzeug)。

WSGI 服务器将原始的 HTTP 请求解析为一个标准的 Python 字典(environ)和一个回调函数,然后调用 Flask 应用实例 app(environ, start_response)

此时:Flask 还没有开始处理业务逻辑,它只是拿到了原始数据。


第二阶段:推入上下文 (Context Push) ⭐ 核心机制

这是 Flask 最神奇的地方。为了让你能在任何地方方便地使用 requestcurrent_app,Flask 必须建立上下文环境

  1. 创建请求上下文 (Request Context):包含 request(请求对象)和 session(会话对象)。
  2. 创建应用上下文 (Application Context):包含 current_app(当前应用实例)和 g(全局临时存储对象)。
  3. 压栈 (Push):将这两个上下文对象推入当前线程/协程的栈中。

此时:你才能在代码中安全地访问 request.argscurrent_app.config,而不用担心多线程冲突。


第三阶段:前置处理 (Before Request)

在正式进入你的视图函数之前,Flask 会按顺序执行所有注册了 @app.before_request 的函数。

典型用途

  • 检查用户是否登录。
  • 打开数据库连接。
  • 记录请求开始时间。
  • 加载当前用户信息并存入 g.user
纯文本
plaintext
@app.before_request
def load_user():
    # 如果 session 中有 user_id,则查询用户并存入 g
    if 'user_id' in session:
        g.user = User.query.get(session['user_id'])
    else:
        g.user = None

注意:如果任何一个 before_request 函数返回了一个响应(例如重定向到登录页),Flask 将跳过后续的视图函数和剩余的 before_request,直接进入第五阶段(After Request)


第四阶段:路由匹配与视图执行 (View Function)

  1. 路由匹配:Flask 根据请求的 URL 路径和 HTTP 方法(GET/POST),在路由表中查找匹配的视图函数。
    • 如果找不到:触发 404 Not Found 错误处理器。
    • 如果找到:准备调用该函数。
  2. 执行视图函数
    • 从 URL 中提取动态参数(如 <int:id>)。
    • 调用你的 Python 函数。
    • 在函数内部,你可以查询数据库、处理表单、调用 API 等。
    • 函数最终返回一个值(字符串、JSON、Response 对象等)。
纯文本
plaintext
@app.route('/post/<int:post_id>')
def show_post(post_id):
    # 这里可以使用 g.user (来自 before_request)
    post = Post.query.get_or_404(post_id)
    return render_template('post.html', post=post, user=g.user)

第五阶段:后置处理与清理 (After & Teardown)

无论视图函数是否成功执行,甚至是否发生了异常,以下两个步骤都会执行:

1. After Request (@app.after_request)

  • 触发条件:只有当请求没有抛出未捕获异常时才执行。
  • 作用:修改响应对象。
  • 典型用途
    • 添加自定义 Header(如 CORS 头、X-Frame-Options)。
    • 记录请求耗时。
    • 压缩响应内容。
纯文本
plaintext
@app.after_request
def add_security_headers(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    return response  # 必须返回 response 对象

2. Teardown Request (@app.teardown_request)

  • 触发条件总是执行,即使发生了异常。
  • 作用:清理资源。
  • 典型用途
    • 关闭数据库连接。
    • 回滚未提交的数据库事务(防止锁死)。
纯文本
plaintext
@app.teardown_request
def close_db(exception):
    if exception:
        db.session.rollback() # 发生错误时回滚
    db.session.remove()       # 移除会话

第六阶段:弹出上下文与返回响应 (Context Pop & Response)

  1. 转换响应:Flask 将视图函数或 after_request 返回的值转换为标准的 WSGI 响应格式。
  2. 弹出上下文 (Pop Context)
    • 将请求上下文和应用上下文从栈中弹出。
    • 重要:此时 requestsessiong 等对象失效。如果你尝试在 teardown 之后访问它们,会报错。
  3. 发送响应:WSGI 服务器将字节流发送回客户端浏览器。

💡 总结:数据流向表

阶段可用对象典型操作是否可中断流程?
Before Requestrequest, session, g, current_app鉴权、打开 DB (返回 Response 即中断)
View Functionrequest, session, g, current_app业务逻辑、查库否 (除非抛出异常)
After Requestresponse, request, g修改 Header、日志
Teardown Requestexception (如果有)关闭 DB、回滚事务

🚀 为什么理解这个很重要?

  1. 调试 Bug:当你遇到 RuntimeError: Working outside of application context 时,你知道是因为你在上下文弹出后(或在上下文推入前)访问了 request
  2. 性能优化:你知道 before_request 会在每个请求都运行,所以不要在里面放耗时的操作(如复杂的数据库查询),除非必要。
  3. 资源管理:你知道必须在 teardown_request 中关闭数据库连接,否则会导致连接泄漏,最终拖垮服务器。

掌握了请求生命周期,你就真正看懂了 Flask 的“内功心法”。接下来,你可以尝试通过编写一个简单的中间件扩展来实践这些概念!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注