Request-Response Lifecycle — Core Concepts

The full journey

When a client sends an HTTP request to a Python web application, it passes through multiple layers before your code handles it and multiple layers again on the way back. Understanding this lifecycle is key to debugging slow responses, middleware ordering issues, and unexpected behavior.

Layer 1: Network and reverse proxy

Before Python sees anything, the request hits the network infrastructure:

  1. DNS resolution — the client resolves api.example.com to an IP address
  2. TCP connection — a three-way handshake establishes the connection
  3. TLS handshake — if HTTPS, the client and server negotiate encryption
  4. Reverse proxy (Nginx/Caddy) — terminates TLS, adds headers (X-Forwarded-For, X-Request-ID), and forwards to the Python process

The reverse proxy handles concerns that Python servers shouldn’t: TLS termination, static file serving, rate limiting at the connection level, and load balancing across multiple Python workers.

Layer 2: Python server (WSGI or ASGI)

The Python server receives the raw HTTP request and translates it into a Python-friendly format:

WSGI (Web Server Gateway Interface) — the synchronous protocol. Gunicorn and uWSGI speak WSGI. Each request gets its own thread or process. Used by Flask and Django.

ASGI (Asynchronous Server Gateway Interface) — the async evolution. Uvicorn and Hypercorn speak ASGI. Supports async/await, WebSockets, and HTTP/2. Used by FastAPI and Starlette.

The server creates a request object containing the HTTP method, path, headers, query parameters, and body.

Layer 3: Middleware stack

Middleware wraps the application, processing requests before and responses after your code:

Request → Middleware 1 → Middleware 2 → Middleware 3 → Your View
Response ← Middleware 1 ← Middleware 2 ← Middleware 3 ← Your View

Common middleware responsibilities:

  • CORS — adds cross-origin headers
  • Authentication — validates tokens, attaches user info
  • Logging — records request start/end times
  • Error handling — catches exceptions and returns proper error responses
  • Compression — gzip/brotli encodes response bodies

Middleware order matters. Authentication middleware must run before authorization middleware. Logging middleware should wrap everything to capture the full request duration.

Layer 4: Routing

The framework’s router matches the request path and method to a handler function:

  • GET /users/42get_user(user_id=42)
  • POST /orderscreate_order()
  • DELETE /users/42/profiledelete_profile(user_id=42)

Routing involves pattern matching (static paths, path parameters, regex patterns) and method filtering. If no route matches, the framework returns 404.

Layer 5: Dependency injection and validation

Before your handler runs, the framework resolves dependencies and validates inputs:

  • FastAPI: Pydantic validates query parameters, path parameters, and request bodies. Depends() functions resolve database sessions, auth tokens, and service objects.
  • Django: Form/serializer validation runs in the view.
  • Flask: Request parsing happens in the view using request.args, request.json.

Validation failures return 422 (FastAPI) or 400 (Flask/Django) before your business logic executes.

Layer 6: Your code

Finally, your handler function runs. It typically:

  1. Reads the validated request data
  2. Queries a database or calls external services
  3. Applies business logic
  4. Constructs a response object

Layer 7: Response

The response travels back through the same layers in reverse:

  1. Your handler returns data
  2. The framework serializes it (JSON, HTML, etc.)
  3. Middleware processes the response (adds headers, compresses, logs)
  4. The Python server sends the HTTP response
  5. The reverse proxy forwards it to the client
  6. The client receives and processes the response

Common misconception

“My view function is slow, so my API is slow.” The view function is often a small part of total latency. TLS handshakes (50-100ms), DNS lookups (10-50ms), database queries (5-500ms), and middleware processing all contribute. Profiling should measure the entire lifecycle, not just the handler.

One thing to remember: An HTTP request passes through at least 7 layers between the client and your Python code — understanding each layer helps you debug where time and errors are actually happening.

pythonwebhttpwsgiasgimiddleware

See Also

  • Python Aiohttp Client Understand Aiohttp Client through a practical analogy so your Python decisions become faster and clearer.
  • Python Api Client Design Why building your own API client in Python is like creating a TV remote that only has the buttons you actually need.
  • Python Api Documentation Swagger Swagger turns your Python API into an interactive playground where anyone can click buttons to try it out — no coding required.
  • Python Api Mocking Responses Why testing with fake API responses is like rehearsing a play with stand-ins before the real actors show up.
  • Python Api Pagination Clients Why APIs send data in pages, and how Python handles it — like reading a book one chapter at a time instead of swallowing the whole thing.