fastware v0.1.0 /Middleware API Reference
On this page

API reference for fastware middleware: CORS headers, request ID tracing, request timing, trusted host validation, and Vite dev proxy routing.

#Middleware API Reference

All middleware classes are pure ASGI -- no framework dependency beyond fastware's own send_error helper. This makes them streaming-safe (SSE, WebSocket) and avoids the response-buffering issues of BaseHTTPMiddleware-style wrappers.

Built-in middleware is automatically applied by create_app when the corresponding AppConfig fields are set. You can also use these classes directly for custom middleware stacks.

#src.fastware.middleware

Pure ASGI middleware for request tracing, CORS headers, trusted-host validation, and Vite dev proxy routing, all streaming-safe for SSE and WebSocket.

All middleware classes are pure ASGI -- no framework dependency beyond fastware's own send_error helper. This keeps them streaming-safe (SSE, WebSocket) and avoids the response-buffering issues of BaseHTTPMiddleware-style wrappers.

Classes: RequestIDMiddleware — assigns/propagates X-Request-Id per request RequestTimingMiddleware — logs method, path, status, duration; ring buffer CORSMiddleware — preflight OPTIONS + response header injection TrustedHostMiddleware — rejects requests from unlisted Host headers ViteDevProxy — proxies non-API requests to a Vite dev server

#RequestIDMiddleware

Assign or propagate a unique request ID per request.

If the incoming request carries an X-Request-Id header, that value is reused. Otherwise a new UUID4 is generated. The ID is stored in scope["state"]["request_id"] and returned as an X-Request-Id response header.

When structlog is available, contextvars are cleared at the start of each request (preventing context leak from a previous request) and the request ID is bound so all log entries within the request include it.

#RequestTimingMiddleware

Log every HTTP request with method, path, status, and duration.

Wraps send to capture the status code from http.response.start, then uses try/finally so timing fires even for long-lived SSE streams (when the client disconnects the ASGI handler returns).

Args:

  • app: The inner ASGI application.
  • error_log: Optional ErrorLog instance. On 5xx responses the

middleware calls error_log.append(...) to persist the failure for dashboard visibility.

  • exclude_paths: Iterable of path prefixes to exclude from the ring

buffer (e.g. ["/events"]). Excluded requests are still logged, just not stored.

  • maxlen: Maximum number of entries in the ring buffer (default 10000).

#CORSMiddleware

Add CORS headers to responses and handle preflight OPTIONS requests.

Args:

  • app: The inner ASGI application.
  • allow_origins: List of allowed origins (e.g. ["http://localhost:5173"]).

Use ["*"] to allow any origin.

  • allow_methods: HTTP methods to advertise. Defaults to common methods.
  • allow_headers: Request headers the client may send. Defaults to

common headers.

  • allow_credentials: Whether to set Access-Control-Allow-Credentials.

#_cors_headers

python
def _cors_headers(self, origin: str) -> list[tuple[bytes, bytes]]

Build the list of CORS response headers for origin.

#TrustedHostMiddleware

Reject requests whose Host header is not in the allow-list.

Prevents DNS rebinding attacks for servers bound to localhost.

Args:

  • app: The inner ASGI application.
  • allowed_hosts: List of hostnames (with optional port) to allow.

Use ["*"] to disable the check.

#ViteDevProxy

ASGI middleware that proxies unmatched requests to a Vite dev server.

Uses a backend-first routing strategy for HTTP: every request hits the backend first. If the backend returns 404 (no route matched), the request is proxied to Vite instead. This means backend routes like /health or /metrics work without being under an API prefix.

WebSocket upgrades cannot be retried, so they still use prefix-based routing: paths matching api_prefix or /events go to the backend; everything else is proxied to Vite (for HMR).

Args:

  • app: The inner ASGI application.
  • vite_port: Port the Vite dev server is listening on.
  • api_prefix: Path prefix for backend WebSocket routes (default

"/api"). Only used for WebSocket routing decisions.

#_is_api_request

python
def _is_api_request(self, path: str) -> bool

Return True if this path should go to the app, not be proxied.

#close

python
async def close(self) -> None

#_proxy_http

python
async def _proxy_http(self, scope: Scope, send: Send, *, body: bytes=b'') -> None

Forward an HTTP request to the Vite dev server.

The body parameter contains the pre-captured request body (already consumed from receive by the backend during the try-first phase).

#_proxy_ws

python
async def _proxy_ws(self, scope: Scope, receive: Receive, send: Send) -> None

Bidirectional WebSocket proxy to Vite (for HMR).

Uses the websockets library if available. Falls back to closing the connection with an error code if not installed.

#CORS Configuration for a Typical SPA

When building a single-page application with a separate frontend dev server (e.g., Vite on port 5173), you need to configure CORS to allow the frontend origin. The CORSMiddleware handles preflight OPTIONS requests automatically and injects the correct Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers response headers on every cross-origin request:

python
from fastware import Router, create_app, AppConfig

router = Router()

# Option 1: Via AppConfig
app = create_app(
    router,
    config=AppConfig(
        cors_origins=["http://localhost:5173"],
        api_prefix="/api",
        spa_fallback=Path("dist/index.html"),
    ),
)

# Option 2: Using CORSMiddleware directly for full control
from fastware.middleware import CORSMiddleware

app = create_app(
    router,
    middleware=[
        lambda app: CORSMiddleware(
            app,
            allow_origins=["http://localhost:5173", "https://myapp.com"],
            allow_methods=["GET", "POST", "PUT", "DELETE"],
            allow_headers=["authorization", "content-type", "x-csrf-token"],
            allow_credentials=True,
        ),
    ],
)

When cors_origins is set on AppConfig, create_app applies CORSMiddleware automatically with default methods and headers. Use the direct middleware approach when you need to customize allowed methods or headers.

#ViteDevProxy Routing

The ViteDevProxy middleware uses a backend-first routing strategy for HTTP requests, forwarding unmatched paths to the Vite dev server so that frontend assets, HMR WebSocket connections, and backend API routes all work through a single origin without manual proxy configuration:

  1. Every HTTP request hits the fastware backend first.
  2. If the backend returns 404 (no route matched), the request is proxied to the Vite dev server.
  3. This means backend routes like /health or /metrics work without needing an API prefix.

The backend_prefixes parameter controls which additional paths are always routed to the backend for WebSocket connections (since WebSocket upgrades cannot be retried). By default, this includes ["/events"] for SSE endpoints.

python
from fastware import Router, create_app, AppConfig

router = Router()

# Vite dev server on port 5173
# Backend routes: /api/*, /events, /ws/*
app = create_app(
    router,
    config=AppConfig(
        vite_dev_port=5173,
        api_prefix="/api",
    ),
)

# Or with custom backend prefixes for WebSocket routing:
from fastware.middleware import ViteDevProxy

app = create_app(
    router,
    middleware=[
        lambda app: ViteDevProxy(
            app,
            vite_port=5173,
            api_prefix="/api",
            backend_prefixes=["/events", "/ws", "/notifications"],
        ),
    ],
)

For WebSocket connections, prefix-based routing is used: paths matching api_prefix or any backend_prefixes entry go to the backend; everything else is proxied to Vite (for HMR). HTTP requests use the try-backend-first approach regardless of path.