Middleware¶
Middleware base class and built-in implementations.
BaseMiddleware¶
Abstract base class for creating class-based middleware.
BaseMiddleware
¶
Bases: ABC
Base class for class-based middleware.
Subclass this and implement before_request and/or after_response for a cleaner interface than writing raw middleware functions.
Example
class LoggingMiddleware(BaseMiddleware): async def before_request( self, request: Request ) -> Request | GeminiResponse | None: print(f"Request: {request.path}") return None # Continue processing
async def after_response(
self, request: Request, response: GeminiResponse
) -> GeminiResponse:
print(f"Response: {response.status}")
return response
logging_mw = LoggingMiddleware()
@app.middleware async def logging(request, call_next): return await logging_mw(request, call_next)
__call__
async
¶
Process the request through this middleware.
This implements the middleware protocol by calling before_request, then call_next, then after_response.
Source code in src/xitzin/middleware.py
after_response
async
¶
Called after the handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
'Request'
|
The original request. |
required |
response
|
GeminiResponse
|
The response from the handler. |
required |
Returns:
| Type | Description |
|---|---|
GeminiResponse
|
The response to send (can be modified). |
Source code in src/xitzin/middleware.py
before_request
async
¶
Called before the handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
'Request'
|
The incoming request. |
required |
Returns:
| Type | Description |
|---|---|
'Request | GeminiResponse | None'
|
|
'Request | GeminiResponse | None'
|
|
'Request | GeminiResponse | None'
|
|
Source code in src/xitzin/middleware.py
Built-in Middleware¶
TimingMiddleware¶
Tracks request processing time.
TimingMiddleware
¶
Bases: BaseMiddleware
Middleware that tracks request processing time.
Stores the elapsed time in request.state.elapsed_time.
Example
timing_mw = TimingMiddleware()
@app.middleware async def timing(request, call_next): return await timing_mw(request, call_next)
@app.gemini("/") def home(request: Request): elapsed = getattr(request.state, 'elapsed_time', 0) return f"# Response generated in {elapsed:.3f}s"
LoggingMiddleware¶
Logs incoming requests and outgoing responses.
LoggingMiddleware
¶
Bases: BaseMiddleware
Middleware that logs requests and responses.
Example
logging_mw = LoggingMiddleware()
@app.middleware async def logging(request, call_next): return await logging_mw(request, call_next)
Create logging middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logger
|
Callable[[str], None] | None
|
Custom logging function. Defaults to print. |
None
|
Source code in src/xitzin/middleware.py
RateLimitMiddleware¶
Simple in-memory rate limiting.
RateLimitMiddleware
¶
Bases: BaseMiddleware
Simple in-memory rate limiting middleware.
Limits requests per client based on certificate fingerprint or IP.
Example
rate_limit_mw = RateLimitMiddleware(max_requests=10, window_seconds=60)
@app.middleware async def rate_limit(request, call_next): return await rate_limit_mw(request, call_next)
Create rate limit middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_requests
|
int
|
Maximum requests allowed per window. |
10
|
window_seconds
|
float
|
Time window in seconds. |
60.0
|
retry_after
|
int
|
Seconds to tell client to wait. |
30
|
Source code in src/xitzin/middleware.py
UserSessionMiddleware¶
Loads and caches user data from certificate fingerprints.
UserSessionMiddleware
¶
UserSessionMiddleware(
user_loader: Callable[[str], Any]
| Callable[[str], Awaitable[Any]],
cache_size: int = 100,
)
Bases: BaseMiddleware
Middleware that loads and caches user data from certificate fingerprints.
Stores the loaded user in request.state.user. Uses an LRU cache to avoid repeated database lookups for the same user across requests.
Supports both sync and async user_loader functions. Sync loaders are executed in a thread pool to avoid blocking the event loop.
Example with sync loader
from xitzin.middleware import UserSessionMiddleware
def load_user(fingerprint: str) -> User | None: with Session(engine) as session: return session.exec( select(User).where(User.fingerprint == fingerprint) ).first()
user_mw = UserSessionMiddleware(load_user)
@app.middleware async def user_session(request, call_next): return await user_mw(request, call_next)
Example with async loader
async def load_user(fingerprint: str) -> User | None: async with async_session() as session: result = await session.execute( select(User).where(User.fingerprint == fingerprint) ) return result.scalar_one_or_none()
user_mw = UserSessionMiddleware(load_user)
Create user session middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_loader
|
Callable[[str], Any] | Callable[[str], Awaitable[Any]]
|
Function that takes a fingerprint and returns a user object (or None if not found). Can be sync or async. Sync loaders are executed in a thread pool to avoid blocking. |
required |
cache_size
|
int
|
Maximum number of users to cache. Defaults to 100. |
100
|
Source code in src/xitzin/middleware.py
cache_info
¶
Return cache statistics.
Returns information about cache hits, misses, and size.
Example
info = user_middleware.cache_info() print(f"Cache hits: {info.hits}, misses: {info.misses}")
Source code in src/xitzin/middleware.py
clear_cache
¶
Clear all cached users.
Call this after updating user data to ensure fresh lookups.
Example
def update_user(user: User): with Session(engine) as session: session.add(user) session.commit() user_middleware.clear_cache()
Source code in src/xitzin/middleware.py
VirtualHostMiddleware¶
Routes requests to different applications based on hostname.
VirtualHostMiddleware
¶
VirtualHostMiddleware(
hosts: dict[str, "Xitzin"],
*,
default_app: "Xitzin | None" = None,
fallback_status: int = 53,
fallback_handler: Callable[["Request"], Any]
| Callable[["Request"], Awaitable[Any]]
| None = None,
)
Bases: BaseMiddleware
Middleware for hostname-based virtual hosting.
Routes requests to different Xitzin applications based on the hostname in the request URL. Supports exact hostname matches and wildcard patterns.
Example
from xitzin import Xitzin from xitzin.middleware import VirtualHostMiddleware
blog_app = Xitzin(title="Blog") api_app = Xitzin(title="API") main_app = Xitzin(title="Gateway")
@blog_app.gemini("/") def blog_home(request): return "# Blog Home"
@api_app.gemini("/") def api_home(request): return "# API Home"
@main_app.gemini("/") def main_home(request): return "# Main Home"
Create virtual host middleware¶
vhost_mw = VirtualHostMiddleware({ "blog.example.com": blog_app, "*.api.example.com": api_app, }, default_app=main_app)
@main_app.middleware async def vhost(request, call_next): return await vhost_mw(request, call_next)
main_app.run()
Create virtual host middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hosts
|
dict[str, 'Xitzin']
|
Mapping of hostname patterns to Xitzin apps. Keys can be exact hostnames ("example.com") or wildcard patterns ("*.example.com"). Exact matches are checked first, then wildcards in definition order. |
required |
default_app
|
'Xitzin | None'
|
Default app to use when no pattern matches. Takes precedence over fallback_status. |
None
|
fallback_status
|
int
|
Status code to return when no match and no default_app. Defaults to 53 (Proxy Request Refused). Common values: - 53: Proxy Request Refused (default) - 51: Not Found - 59: Bad Request |
53
|
fallback_handler
|
Callable[['Request'], Any] | Callable[['Request'], Awaitable[Any]] | None
|
Custom handler function for unmatched hosts. Receives the request and must return a response. Takes precedence over both default_app and fallback_status. Can be sync or async. |
None
|
Source code in src/xitzin/middleware.py
before_request
async
¶
Route request to appropriate app based on hostname.