Code Around Every Handler
Middleware is code that runs around your handlers: before the request reaches them, after the response leaves them, or instead of them. Logging, authentication, rate limiting, compression — all middleware.
The same idea travels under three names: web frameworks say middleware, gRPC says interceptor, some ecosystems say filter. One concept, three vocabularies.
In Python, middleware is a function that takes a handler function and returns a wrapped one — exactly what Python's own @decorator syntax was made for, and exactly the Decorator pattern from the design patterns group. A stack of them behaves like a Chain of Responsibility.
A Logging Middleware
with_log wraps any handler function: it prints the request coming in, lets the handler work, and prints again on the way out. The handler itself stays completely unaware. functools.wraps keeps the wrapped function's name and docs visible — the same helper every Python decorator uses.
Wrap once, log everything
The wrapped handler serves two different paths and both get logged — the logging is written once, in the wrapper, not in every handler.
Output: complete Python program
import functools
import threading
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
def app(method, path):
if path == "/users":
return "Ana, Ben\n"
if path == "/notes":
return "shopping, ideas\n"
return "nothing here\n"
# Middleware: takes a handler function, returns a wrapped one.
def with_log(next_handler):
@functools.wraps(next_handler)
def wrapped(method, path):
print("-> ", method, path)
body = next_handler(method, path)
print("<- done")
return body
return wrapped
handle = with_log(app)
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = handle("GET", self.path)
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(body.encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
for path in ["/users", "/notes"]:
with urllib.request.urlopen(base + path) as resp:
print("body:", resp.read().decode().strip())
server.shutdown()
Middleware That Stops the Request
Middleware can also refuse: an auth check that finds no valid key answers 401 Unauthorized and never calls the next handler.
Chained as with_log(with_auth(report)), the request passes through the layers in order — and the response bubbles back out through them.
An auth layer in the chain
The request without a key is stopped by the auth layer with 401 — the handler never runs. With the key, the request passes the whole chain and reaches the data.
Output: complete Python program
import threading
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
def report(method, path, headers):
return 200, "the secret report\n"
def with_log(next_handler):
def wrapped(method, path, headers):
print("->", method, path)
return next_handler(method, path, headers)
return wrapped
# Auth middleware: can stop the request before the handler.
def with_auth(next_handler):
def wrapped(method, path, headers):
if headers.get("X-API-Key") != "secret123":
return 401, "missing or wrong API key\n"
return next_handler(method, path, headers)
return wrapped
handle = with_log(with_auth(report))
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
status, body = handle("GET", self.path, self.headers)
self.send_response(status)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(body.encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
for key in ["", "secret123"]:
req = urllib.request.Request(base + "/report")
if key:
req.add_header("X-API-Key", key)
try:
with urllib.request.urlopen(req) as resp:
status, body = resp.status, resp.read().decode()
except urllib.error.HTTPError as err:
status, body = err.code, err.read().decode()
print(" status:", status, "-", body.strip())
server.shutdown()