Flask Request Lifecycle
Understanding the complete lifecycle of a Flask request is key to writing good Flask applications.
This chapter dissects the entire process of an HTTP request from entering the server to returning a response, at the source code level, to help you build a clear execution model.
* * *
## What is the Request Lifecycle
The Request Lifecycle refers to the complete processing procedure an HTTP request goes through inside Flask.
It includes the following stages: Receive Request β Create Context β Preprocess β Route Dispatch β View Execution β Response Building β Context Cleanup β Return Response.
Understanding this process allows you to correctly answer the following questions:
* When is the data in the `g` object available? When is it cleaned up?
* What is the execution order of `before_request` and `after_request`?
* What processing steps are sequentially passed through when an exception occurs?
* Why does using `request` outside of a view function cause an error?
* * *
## Complete Flowchart
The diagram below shows the complete call chain of Flask from receiving a request to returning a response:

* * *
## Step-by-Step Explanation
### Stage 1: WSGI Entry
When an HTTP request reaches the server, the WSGI server (such as Gunicorn, Werkzeug development server) calls the Flask application instance.
Flask implements the `__call__` method, making it a valid WSGI application:
## Example
```python
# Flask source code simplified (actual location: flask/app.py)
# WSGI server calls your application like this:
# response = app(environ, start_response)
class Flask(App):
def __call__(self, environ, start_response):
"""Entry point called by WSGI server"""
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
"""Actual WSGI processing logic"""
# Step 1: Create request context based on environ
ctx = self.request_context(environ)
try:
ctx.push() # Push onto context stack
response = self.full_dispatch_request(ctx) # Complete dispatch
except Exception as e:
response = self.handle_exception(ctx, e) # Exception fallback
finally:
ctx.pop(error) # Pop context, perform cleanup
return response(environ, start_response)
> Flask designs the entry point to call the `wsgi_app` method, rather than writing logic directly in `__call__`. This allows you to insert middleware by replacing `app.wsgi_app`, without affecting calls to the `app` instance itself.
### Stage 2: Creating Context
`request_context(environ)` creates an `AppContext` object based on WSGI environment variables.
This object contains all information about the current request. Once it is pushed onto the stack, global proxies such as `request`, `session`, `g`, and `current_app` begin pointing to the current request's data.
## Example
```python
# Simple experiment to understand context
from flask import Flask, request, current_app
app = Flask(__name__)
with app.app_context():
# Only application context β current_app is available, request is not
print(current_app.name) # β Works
# print(request.method) # β Error: Working outside of request context
with app.test_request_context("/hello?name=tutorial"):
# Request context also includes application context β both are available
print(current_app.name) # β Outputs app name
print(request.path) # β Outputs /hello
print(request.args) # β Outputs tutorial
| Context Type | When Created | When Destroyed | Available Global Variables |
| --- | --- | --- | --- |
| Application Context | When request arrives / manually with `app.app_context()` | When request ends / `with` block exits | `current_app`, `g` |
| Request Context | When request arrives / manually with `app.test_request_context()` | When request ends / `with` block exits | `request`, `session` (includes all variables from application context) |
> The most common mistake beginners make is accessing `request` directly at the module level. Remember: `request` only exists within a request context. You can only access it inside a view function (or functions called by it).
### Stage 3: preprocess_request (Preprocessing)
After the context is ready, `full_dispatch_request` first calls `preprocess_request`.
This stage executes all functions registered with `@app.before_request`.
If any `before_request` function returns a non-`None` value, Flask treats it as the final response, skipping subsequent view function execution.
## Example
```python
from flask import Flask, request, session, redirect, url_for
app = Flask(__name__)
app.secret_key = "dev-secret"
@app.before_request
def check_authentication():
"""Check if user is logged in before each request"""
# Skip authentication check for login and registration pages
if request.endpoint in ("login", "register", "static"):
return None # None means continue normal flow
# If not logged in, directly return redirect response (skip view function)
if "username" not in session:
return redirect(url_for("login"))
# User is logged in, continue normally
return None
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
session = request.form
return redirect(url_for("dashboard"))
return ''
@app.route("/register")
def register():
return "Registration page (no login required)"
@app.route("/dashboard")
def dashboard():
return f"Welcome {session['username']} to the TUTORIAL console"
### Stage 4: dispatch_request (Route Dispatching)
After preprocessing, Flask enters the core dispatching stage:
1. Match URL to corresponding routing rule (Rule)
2. Extract variable parameters from URL (view_args)
3. Call the corresponding view function, passing extracted parameters
## Example
```python
# Source code simplified: dispatch_request workflow
def dispatch_request(self, ctx):
req = ctx.request
# Get matched routing rule (Rule object)
rule = req.url_rule
# Get variable parameters from URL. E.g., /post/42 β {"post_id": 42}
view_args = req.view_args
# Find corresponding function from view_functions dictionary and call it
return self.view_functions[rule.endpoint](**view_args)
When matching fails, a `RoutingException` is raised, eventually captured by the error handler and returning 404.
### Stage 5: View Function Execution
This is where you write business logic. Flask calls your view function and obtains the return value.
The return value can be of various types β string, dict, tuple, Response object β all of which will be uniformly processed in the next stage.
### Stage 6: finalize_request (Response Building)
After the view function completes, `finalize_request` is responsible for converting the return value into a standard Response object:
## Example
```python
# Source code simplified: finalize_request workflow
def finalize_request(self, ctx, rv):
# 1. Call make_response to convert various return types to Response object
response = self.make_response(rv)
# 2. Execute after_request hooks via process_response
response = self.process_response(ctx, response)
# 3. Send signal: request_finished
request_finished.send(self, response=response)
return response
`make_response` conversion rules:
| Return Type | Conversion Method |
| --- | --- |
| str / bytes | As response body, Content-Type: text/html |
| dict / list | Call `jsonify()`, Content-Type: application/json |
| tuple | Parsed as (body, status) or (body, headers) |
| Response object | Used directly |
| iterator / generator | As streaming response |
### Stage 7: after_request Hook
`process_response`, after executing `make_response`, sequentially calls all functions registered with `@app.after_request`.
Each `after_request` function receives and returns a Response object, and can modify response headers, add cookies, etc.
## Example
```python
# Typical after_request usage: add security headers to each response
@app.after_request
def add_security_headers(response):
response.headers = "nosniff"
response.headers = "DENY"
response.headers = "1; mode=block"
return response # Must return response object
# Also commonly used for adding CORS headers
@app.after_request
def add_cors_headers(response):
response.headers = "*"
response.headers = "GET, POST, PUT, DELETE"
return response
### Stage 8: Context Cleanup
After the response is sent, `ctx.pop()` in the `finally` block performs context cleanup:
1. Call all functions registered with `teardown_request` (executed regardless of request success or failure)
2. Call all functions registered with `teardown_appcontext`
3. Send `request_tearing_down` and `appcontext_tearing_down` signals
4. Release all data stored in the `g` object
## Example
```python
# Typical teardown usage: close database connection
@app.teardown_appcontext
def close_database_connection(error):
"""Ensure database connection is closed even if request errors"""
db = g.pop("db", None)
if db is not None:
db.close()
# error parameter: if there was an exception during request, it is received here
if error:
app.logger.warning(f"Request exception, database connection closed: {error}")
# Difference between teardown and after_request
@app.after_request
def after(response):
# after_request executes in normal flow, not when exception occurs
# Used for modifying successful responses
return response
@app.teardown_request
def teardown(error):
# teardown always executes β regardless of request success or failure
# Used for cleaning up resources (closing files, releasing locks, etc.)
pass
* * *
## Exception Handling Flow
When a view function raises an exception, Flask has a dedicated exception handling path:
1. `full_dispatch_request` catches the exception
2. Calls `handle_user_exception` to determine exception type
3. HTTP exceptions (such as `abort(404)`) β find corresponding `errorhandler`
4. Ordinary exceptions β find `errorhandler` for the exception class
5. Handler not found β call `handle_exception`, return 500 error
6. Regardless of exception or not, finally goes through `finalize_request` to build response
> Key point in exception path: `after_request` hooks also execute for error responses (from Flask 1.1 onwards), but a non-`None` return value from `before_request` short-circuits the entire process, and subsequent view functions and hooks will not execute.
* * *
## Hook Function Quick Reference
| Hook | Registration Method | Execution Timing | Typical Use |
| --- | --- | --- | --- |
| before_request | `@app.before_request` | Before route dispatch | Login check, request logging, permission verification |
| before_first_request | `@app.before_request` (deprecated) | Before first request (only once) | Initialization operations (recommended to use factory pattern instead) |
| after_request | `@app.after_request` | After response built, before sent | Add response headers, CORS, logging |
| teardown_request | `@app.teardown_request` | When request context popped (always executes) | Release resources, close files |
| teardown_appcontext | `@app.teardown_appcontext` | When application context popped (always executes) | Close database connections, clean up cache |
| errorhandler | `@app.errorhandler(code)` | When corresponding error code/exception occurs | Custom error pages |
* * *
## Signal Events
Flask uses the blinker library to send signals at key points:
## Example
```python
from flask import Flask, signals
app = Flask(__name__)
# Connect signal β log when request arrives
@signals.request_started.connect_via(app)
def log_request_start(sender, **extra):
print("Request started")
# Connect signal β log when request completes
@signals.request_finished.connect_via(app)
def log_request_end(sender, response, **extra):
print(f"Request ended, response status code: {response.status_code}")
# Connect signal β capture exceptions
@signals.got_request_exception.connect_via(app)
def log_exception(sender, exception, **extra):
print(f"Exception occurred during request: {exception}")
| Signal | Trigger Timing |
| --- | --- |
| request_started | After request context established, before preprocessing |
| request_finished | After response built, before sent |
| got_request_exception | When exception occurs during request processing |
| request_tearing_down | When request context destroyed |
| appcontext_pushed | When application context pushed onto stack |
| appcontext_popped | When application context popped from stack |
| message_flashed | When `flash()` called |
| template_rendered | When template rendering successful |
* * *
## Key Points Summary
> Core rule: In a complete request lifecycle, `before_request` β view function β `after_request` β `teardown` these four nodes will always execute in order (`teardown` always
YouTip