Middleware API¶
The middleware module defines the middleware system for Plinx applications. Middleware components process requests before they reach handlers and responses before they're returned to clients.
Middleware Class¶
plinx.middleware.Middleware
¶
Base class for all Plinx middleware components.
The middleware system in Plinx follows a nested pattern where each middleware wraps the application or another middleware component. This allows for a chain of processing both before a request reaches the application and after the application generates a response.
Middleware classes should inherit from this base class and override the
process_request and process_response methods to implement custom behavior.
The middleware execution flow works like this:
1. Client request comes in
2. Each middleware's process_request is called from outermost to innermost
3. The application handles the request
4. Each middleware's process_response is called from innermost to outermost
5. The response is sent back to the client
Examples:
class LoggingMiddleware(Middleware):
def process_request(self, request):
print(f"Request: {request.path}")
def process_response(self, request, response):
print(f"Response: {response.status_code}")
app = Plinx()
app.add_middleware(LoggingMiddleware)
Middleware that modifies the request or response:
class AuthMiddleware(Middleware):
def process_request(self, request):
request.user = None
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header[7:]
request.user = self.get_user_from_token(token)
def get_user_from_token(self, token):
# Implementation to validate token and return user
pass
Source code in plinx/middleware.py
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
Functions¶
__call__(environ, start_response)
¶
WSGI callable interface for the middleware.
This method makes middleware instances callable according to the WSGI spec, allowing them to be used in a WSGI server. It creates a Request object, passes it to the application's handle_request method, and returns the response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
environ
|
dict
|
The WSGI environment dictionary |
required |
start_response
|
callable
|
The WSGI start_response callable |
required |
Returns:
| Type | Description |
|---|---|
|
An iterable of bytes representing the response body |
Source code in plinx/middleware.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
__init__(app)
¶
Initialize the middleware with a WSGI application.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
A WSGI application (typically a Plinx instance or another middleware) |
required |
Source code in plinx/middleware.py
53 54 55 56 57 58 59 60 61 62 63 | |
add(middleware_cls)
¶
Add a new middleware to the stack.
This method creates an instance of the provided middleware class, passing the current middleware instance (or application) as the app parameter. This builds up a chain of nested middleware instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
middleware_cls
|
A class inheriting from Middleware |
required |
Source code in plinx/middleware.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
handle_request(request)
¶
Process a request through this middleware and the wrapped application.
This method implements the middleware chain by: 1. Calling this middleware's process_request method 2. Passing the request to the wrapped application/middleware 3. Calling this middleware's process_response method with the response
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The WebOb Request object |
required |
Returns:
| Type | Description |
|---|---|
|
The Response object after processing |
Source code in plinx/middleware.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
process_request(request)
¶
Process the request before it reaches the application.
Override this method in your middleware subclass to modify or inspect the request before it's handled by the application or the next middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The WebOb Request object |
required |
Source code in plinx/middleware.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
process_response(request, response)
¶
Process the response after it's generated by the application.
Override this method in your middleware subclass to modify or inspect the response before it's returned to the client or the previous middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The WebOb Request object that generated this response |
required |
response
|
Response
|
The Response object to be returned |
required |
Source code in plinx/middleware.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
Custom Middleware Examples¶
Simple Logging Middleware¶
from plinx.middleware import Middleware
class LoggingMiddleware(Middleware):
def process_request(self, request):
print(f"Incoming request: {request.method} {request.path}")
def process_response(self, request, response):
print(f"Outgoing response: {response.status_code}")
Request Timer Middleware¶
import time
from plinx.middleware import Middleware
class TimerMiddleware(Middleware):
def process_request(self, request):
request.start_time = time.time()
def process_response(self, request, response):
if hasattr(request, "start_time"):
duration = time.time() - request.start_time
print(f"Request took {duration:.6f} seconds")
# Add timing header
response.headers["X-Request-Duration"] = f"{duration:.6f}"
Authentication Middleware¶
from plinx.middleware import Middleware
class AuthMiddleware(Middleware):
def process_request(self, request):
request.user = None
# Check for authentication header
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header[7:]
try:
# In a real app, you'd verify the token
user_id = self.verify_token(token)
request.user = {"id": user_id}
except Exception as e:
print(f"Auth error: {str(e)}")
def verify_token(self, token):
# Simplified demo - in reality, you'd verify with your auth system
if token == "valid_demo_token":
return 1
raise Exception("Invalid token")
CORS Middleware¶
from plinx.middleware import Middleware
class CORSMiddleware(Middleware):
def __init__(self, app, allowed_origins=None):
super().__init__(app)
self.allowed_origins = allowed_origins or ["*"]
def process_response(self, request, response):
origin = request.headers.get("Origin", "")
# Check if the origin is allowed
if "*" in self.allowed_origins or origin in self.allowed_origins:
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
# Handle preflight OPTIONS requests
if request.method == "OPTIONS":
response.status_code = 200
return response
Using Multiple Middleware¶
You can add multiple middleware components to a Plinx application:
from plinx import Plinx
from plinx.middleware import Middleware
class Middleware1(Middleware):
def process_request(self, request):
print("Middleware 1: process_request")
def process_response(self, request, response):
print("Middleware 1: process_response")
class Middleware2(Middleware):
def process_request(self, request):
print("Middleware 2: process_request")
def process_response(self, request, response):
print("Middleware 2: process_response")
app = Plinx()
# Order matters! Middleware1 will be called before Middleware2 for requests,
# but after Middleware2 for responses
app.add_middleware(Middleware1)
app.add_middleware(Middleware2)
This will produce the following execution order for a request: 1. Middleware1.process_request 2. Middleware2.process_request 3. Handler processes the request 4. Middleware2.process_response 5. Middleware1.process_response
Short-Circuiting Requests¶
A middleware can short-circuit the request processing by returning a response:
from plinx.middleware import Middleware
from plinx.status_codes import StatusCodes
class AuthRequiredMiddleware(Middleware):
def process_request(self, request):
auth_header = request.headers.get("Authorization", "")
# If no auth header, short-circuit the request
if not auth_header:
# Need to access the app to create a response
response = self.app.handle_request(request)
response.status_code = StatusCodes.UNAUTHORIZED.value
response.json = {"error": "Authentication required"}
return response # Short-circuit
Error Handling in Middleware¶
from plinx.middleware import Middleware
from plinx.status_codes import StatusCodes
class ErrorCatchingMiddleware(Middleware):
def process_request(self, request):
try:
# Normal request processing
pass
except Exception as e:
print(f"Request error: {str(e)}")
def process_response(self, request, response):
# You can modify the response based on conditions
if response.status_code >= 500:
response.json = {
"error": "An internal error occurred",
"status_code": response.status_code
}
# Log the error
print(f"Server error occurred: {response.status_code}")