Skip to content
src.fastware.middleware
Edit
On this page

Pure-ASGI middleware: CORS, request-ID tracing, request timing with error-log integration, trusted-host validation, and backend-first ViteDevProxy.

#src.fastware.middleware

#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.

Raises:

  • ValueError: if allow_origins contains "*" while

allow_credentials is True. Because this middleware echoes the request origin, that combination would grant credentialed cross-origin access to any site.

#_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: requests hit the backend first, streaming the response through message-by-message (SSE-safe). Only the http.response.start message is held back until the status is known; if the backend returns 404 (no route matched), the backend's response is discarded and the request is replayed to Vite instead. This means backend routes like /health or /metrics work without being under an API prefix.

Paths matching api_prefix or backend_prefixes always go straight to the backend with no 404-retry — their 404s belong to the client. WebSocket upgrades cannot be retried, so they use the same prefix rule: matching paths go to the backend; everything else is proxied to Vite (for HMR).

#__init__

python
def __init__(self, app: Callable, *, vite_port: int, api_prefix: str='/api', backend_prefixes: list[str] | None=None) -> None

Wrap app with backend-first Vite dev proxying.

Args:

  • app: The inner ASGI application (the fastware app) handled

first for every request.

  • vite_port: Port the Vite dev server is listening on; unmatched

requests are proxied there.

  • api_prefix: Path prefix for backend routes that always go

straight to the backend without a 404-retry (default "/api").

  • backend_prefixes: Additional backend path prefixes routed to

the app, notably for WebSocket upgrades (default ["/events", "/ws"]). /ws is the conventional app WebSocket path; routing it to the backend does not interfere with Vite HMR, whose websocket connects at / (identified by the vite-hmr subprotocol), not /ws.

#_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.

The reserved /__fastware/ namespace (version endpoint and any future diagnostics) is always a backend prefix -- never proxied to Vite -- so those endpoints behave identically in dev and prod. This also matters for WebSocket upgrades, which cannot be 404-retried.

#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 it is not installed, or the proxy connection fails, the client connection is closed with code 1011 (internal error) and a reason -- never a clean 1000 close that would hide the failure.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search