WSGI Middleware

WSGI middleware sits between a WSGI server and a WSGI application. Its primary purpose is to process requests before they reach the application and to modify the responses before they are sent back to the client. Middleware provides a way to add additional functionality to web applications without changing the application’s core logic.

Some common uses of WSGI middleware include:

  1. Authentication: Ensuring that a user is authenticated before reaching the main application.
  2. Logging: Logging details of incoming requests and outgoing responses.
  3. Caching: Storing responses so they can be served more quickly for repeated requests.
  4. Compression: Compressing responses to reduce data size before sending them to the client

How WSGI Middleware Works

WSGI middleware acts like both a server and an application. It receives the request from the server and, in turn, calls the next application in the chain. It can also modify the environ (request data) and the response returned by the application.

Key Concept:

A WSGI middleware wraps the original WSGI application and has the same function signature: it takes environ and start_response as arguments and returns an iterable as a response.

Basic Structure of WSGI Middleware

Here’s a simple example of WSGI middleware that logs the time taken by the application to handle the request:

import time
from typing import Callable, List, Tuple

def simple_middleware(app: Callable):
    def middleware(environ: dict, start_response: Callable) -> List[bytes]:
        # Middleware pre-processing logic
        start_time = time.time()
        
        # Call the WSGI application
        response = app(environ, start_response)
        
        # Middleware post-processing logic
        duration = time.time() - start_time
        print(f"Request handled in {duration:.4f} seconds")
        
        # Return the application response
        return response
    return middleware

# Example of using the middleware with a WSGI application
def simple_app(environ: dict, start_response: Callable) -> List[bytes]:
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b"Hello from WSGI Application!"]

# Wrap the application with the middleware
app_with_middleware = simple_middleware(simple_app)

In this example, simple_middlewareis the middleware function that wraps around the WSGI application simple_app. Before the application is called, the middleware captures the current time. After the application has processed the request, the middleware calculates and logs the time taken. The middleware can modify or log both the incoming request (environ) and the outgoing response.

Common Use Cases for WSGI Middleware

1. Authentication Middleware

This middleware ensures that requests are authenticated before they reach the WSGI application.

def auth_middleware(app: Callable):
    def middleware(environ: dict, start_response: Callable):
        # Check if the user is authenticated
        if environ.get('HTTP_AUTHORIZATION') != 'Bearer valid_token':
            # Return a 403 Forbidden response if authentication fails
            start_response('403 Forbidden', [('Content-Type', 'text/plain')])
            return [b"Forbidden"]
        # Call the WSGI application if authentication succeeds
        return app(environ, start_response)
    return middleware

Here, the middleware checks if the HTTP_AUTHORIZATION header contains a valid token. If it doesn’t, it returns a 403 Forbidden response without invoking the application.

2. Logging Middleware

This middleware logs the details of each request.

def logging_middleware(app: Callable):
    def middleware(environ: dict, start_response: Callable):
        # Log request method and path
        print(f"Request: {environ['REQUEST_METHOD']} {environ['PATH_INFO']}")
        
        # Call the application
        return app(environ, start_response)
    return middleware

This middleware logs the HTTP request method (e.g., GET, POST) and the request path before passing the request to the application.

3. Response Header Modification Middleware

This middleware adds or modifies headers in the response returned by the WSGI application.

def header_middleware(app: Callable):
    def middleware(environ: dict, start_response: Callable):
        def custom_start_response(status: str, headers: List[Tuple[str, str]]):
            # Add a custom header
            headers.append(('X-Custom-Header', 'MyValue'))
            return start_response(status, headers)
        
        # Call the WSGI application with a custom start_response
        return app(environ, custom_start_response)
    return middleware

In this example, the middleware adds an X-Custom-Headerto every HTTP response.

Chaining Middleware

You can chain multiple middleware components together by applying one middleware after another. For example:

# Define the WSGI application
def simple_app(environ: dict, start_response: Callable) -> List[bytes]:
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b"Hello from WSGI Application!"]

In this chain:

  1. The header middleware adds a custom header.
  2. The authentication middleware checks if the request is authorized.
  3. The logging middleware logs each request.

Track your progress

Mark this subtopic as completed when you finish reading.