A robust, streaming-capable reverse proxy for FastAPI/Starlette with built-in Latency-Based Load Balancing and Active Health Monitoring.
asyncio.CancelledError to prevent resource leaks.websockets library version differences (12.0+ vs Legacy).The recommended way to use the library is within a FastAPI lifespan handler. This ensures all background monitoring tasks and HTTP clients start and stop cleanly.
from fastapi import FastAPI, Request, WebSocket
from contextlib import asynccontextmanager
from fastapi_reverse_proxy import (
HealthChecker, LoadBalancer,
create_httpx_client, close_httpx_client
)
# 1. Setup health monitoring and load balancing
checker = HealthChecker(["http://localhost:8080", "http://localhost:8081"])
lb = LoadBalancer(checker)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Initialize global resources
await create_httpx_client(app)
async with checker: # Starts background health loop
yield
await close_httpx_client(app)
app = FastAPI(lifespan=lifespan)
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def gateway(request: Request, path: str):
# Route to the fastest healthy backend
return await lb.proxy_pass(request, path=f"/{path}")
@app.websocket("/ws/{path:path}")
async def ws_tunnel(websocket: WebSocket, path: str):
# Automatic subprotocol negotiation + Tunneling
await lb.proxy_pass_websocket(websocket, path=f"/{path}")
The proxy_pass function and LoadBalancer.proxy_pass provide deep customization for upstream requests:
| Parameter | Type | Description |
|---|---|---|
method | str | Force a specific HTTP method (e.g., "POST"). |
override_body | bytes | Send custom data instead of the incoming request body. |
additional_headers | dict | Append custom headers to the proxied request. |
override_headers | dict | Use these headers instead of original request headers. |
forward_query | bool | Whether to append the incoming query string (Default: True). |
The proactive component. It owns an internal asyncio background task that monitors backends.
async with block (or call start()), the checker performs an immediate check of all backends. This eliminates the "cold-start" window where backends are unknown.HealthChecker(["http://a", "http://b"], ping_path="/health")checker = HealthChecker([
{"host": "http://api-1", "pingpath": "/v1/status", "maxrequests": 100},
{"host": "http://api-2", "pingpath": "/health"}
])
ping_path: Get or set the global health check path (default: "/").A normal Python object that makes routing decisions based on its source.
HealthChecker (or a static list) for data and doesn't need explicit start/stop calls.The library implements "deferred negotiation" for WebSockets:
scope.websocket.accept(subprotocol=...) back to the client.httpx clients and sockets) is triggered even on task cancellation (BaseException).inspect.signature to automatically detect version-specific parameters in the websockets library.This project allows you to create a reverse proxy with advanced functions.
Whether you just wanna use proxy_pass or proxy_pass_websocket, you can do it with this library.
If you wanna use it in production, you can use it with HealthChecker and LoadBalancer.