YouTip LogoYouTip

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: ![Image 1: Flask Request Processing Lifecycle](#) * * * ## 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
← Flask Blueprint ApiFlask Database β†’