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:
- Authentication: Ensuring that a user is authenticated before reaching the main application.
- Logging: Logging details of incoming requests and outgoing responses.
- Caching: Storing responses so they can be served more quickly for repeated requests.
- 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:
- The header middleware adds a custom header.
- The authentication middleware checks if the request is authorized.
- The logging middleware logs each request.