brainwires-proxy
Protocol-agnostic proxy framework for debugging and transforming application traffic.
Overview
brainwires-proxy is a composable, async-first proxy framework built on Tokio. It supports multiple transport protocols, pluggable middleware, format conversion, and live traffic inspection — all behind a fluent builder API.
Design principles:
- Standalone — no dependency on
brainwires-coreor the rest of the Brainwires ecosystem - Composable — mix and match transports, middleware, and converters via traits
- Async-native — built entirely on
tokio,futures, andasync-trait
┌───────────────────────────────────────────────┐
│ ProxyService │
│ │
Client ──────► Listener ──► Middleware Stack ──► Connector ──────► Upstream
│ (onion model) │
│ │
│ ┌──────────┐ ┌────────────┐ │
│ │Inspector │ │ Conversion │ │
│ │ Store │ │ Registry │ │
│ └──────────┘ └────────────┘ │
└───────────────────────────────────────────────┘
Quick Start
Add to your Cargo.toml:
[]
= "0.1"
Minimal HTTP reverse proxy:
use *;
async
Features
| Feature | Default | Description |
|---|---|---|
http |
Yes | HTTP/1.1 and HTTP/2 transport via Hyper |
websocket |
No | WebSocket transport via tokio-tungstenite |
tls |
No | TLS termination via rustls |
inspector-api |
No | HTTP API for querying captured traffic events |
full |
No | Enables all features |
TCP and Unix socket transports are always available regardless of feature flags.
Enable features in Cargo.toml:
# Pick what you need
= { = "0.1", = ["websocket", "inspector-api"] }
# Or enable everything
= { = "0.1", = ["full"] }
Architecture
Transport
Defines how the proxy accepts inbound connections and forwards them upstream.
Traits:
TransportListener— accepts inbound connections, produces(ProxyRequest, oneshot::Sender<ProxyResponse>)pairsTransportConnector— forwards aProxyRequestto the upstream and returns aProxyResponse
Built-in implementations:
| Struct | Feature | Direction | Description |
|---|---|---|---|
HttpListener |
http |
Inbound | HTTP/1.1 + HTTP/2 server via Hyper |
HttpConnector |
http |
Outbound | HTTP client, combines upstream base URL with request path |
WebSocketListener |
websocket |
Inbound | WebSocket server, each message becomes a ProxyRequest |
TcpRawListener |
— | Inbound | Raw TCP, reads full payload into a single request body |
UnixListener |
— | Inbound | Unix domain socket, auto-cleans stale socket files |
SSE utilities (is_sse_response, parse_sse_chunk, serialize_sse_event) are also provided for working with Server-Sent Events streams.
Middleware
Middleware follows an onion model: requests flow forward through layers, responses flow back in reverse order.
Trait:
LayerAction::Forward(req) continues to the next layer; LayerAction::Respond(resp) short-circuits the chain.
Built-in layers:
| Layer | Description |
|---|---|
LoggingLayer |
Structured request/response logging via tracing, optional body capture |
InspectorLayer |
Captures traffic events into EventStore and broadcasts to subscribers |
RateLimitLayer |
Token-bucket rate limiter, returns 429 when exhausted |
AuthLayer |
Auth strategies: StaticBearer, Passthrough, Validate, Strip |
HeaderInjectLayer |
Set, append, or remove headers on requests and/or responses |
Conversion
Transform request/response bodies between formats with auto-detection.
Traits:
Converter— converts a complete body (Bytes → Bytes) between a source and targetFormatIdStreamConverter— converts chunk-by-chunk for streaming scenariosFormatDetector— inspects body bytes and/orContent-Typeto determine the format
ConversionRegistry ties them together: register converters and detectors, then call registry.convert(body, source, target, content_type) — it auto-detects the source format if not provided.
Built-in components:
| Struct | Description |
|---|---|
GenericJsonDetector |
Detects any valid JSON (by content-type or syntax) |
JsonFieldDetector |
Detects JSON containing specific fields |
JsonTransformer |
Applies JsonRule transforms: rename, remove, set, wrap, unwrap fields |
JsonRule variants:
| Rule | Description |
|---|---|
RenameField { from, to } |
Rename a top-level field |
RemoveField(path) |
Remove a field |
SetField { path, value } |
Set a field to a value (dot-separated paths) |
WrapIn(key) |
Wrap the entire body as a nested object |
Unwrap(key) |
Extract a nested value as the new root |
Inspector
Captures and broadcasts traffic events for debugging and analysis.
EventStore— ring-buffer storage with configurable capacity, auto-evicts oldest eventsEventBroadcaster—tokio::sync::broadcast-based live event fan-out to subscribersTrafficEvent— event record containing request ID, timestamp, direction, and kindEventFilter— query by direction, request ID, event kind, timestamp, or limit
TrafficEventKind variants: Request, Response, SseEvent, WebSocketMessage, Error, Connection, Conversion
Usage Examples
HTTP Reverse Proxy with Logging
use *;
let proxy = new
.listen_on
.upstream_url
.with_body_logging // logs request + response bodies
.build?;
proxy.run.await?;
Auth Token Injection
use ;
let proxy = new
.listen_on
.upstream_url
.layer
.with_logging
.build?;
Rate Limiting
use RateLimitLayer;
let proxy = new
.listen_on
.upstream_url
.layer
.build?;
Header Manipulation
use ;
use ;
let proxy = new
.listen_on
.upstream_url
.layer
.build?;
Traffic Inspection with Live Broadcast
use *;
use SocketAddr;
let proxy = new
.listen_on
.upstream_url
.with_inspector
.with_inspector_api
.inspector_capacity
.build?;
// Access captured events programmatically
let store = proxy.event_store.clone;
let broadcaster = proxy.event_broadcaster.clone;
// Subscribe to live events
let mut rx = broadcaster.subscribe;
spawn;
proxy.run.await?;
Custom Middleware
use *;
use async_trait;
;
let proxy = new
.listen_on
.upstream_url
.layer
.build?;
Format Conversion with Auto-Detection
use ;
use FormatId;
let mut registry = new;
// Detect API responses by field presence
registry.register_detector;
// Transform: unwrap nested "data" field to top level
let transformer = new;
Validate Inbound Auth
use AuthLayer;
// Reject requests without a valid token (returns 401)
let proxy = new
.listen_on
.upstream_url
.layer
.build?;
Configuration
ProxyConfig can be built programmatically or deserialized from JSON/TOML:
Listener variants:
| Variant | Fields | Default |
|---|---|---|
Tcp |
addr: SocketAddr |
127.0.0.1:8080 |
Unix |
path: PathBuf |
— |
Upstream variants:
| Variant | Fields | Default |
|---|---|---|
Url |
url: String |
http://localhost:3000 |
Tcp |
host: String, port: u16 |
— |
Unix |
path: PathBuf |
— |
JSON example:
Inspector API
When inspector-api is enabled and an API address is configured, two HTTP endpoints are exposed:
| Endpoint | Method | Description |
|---|---|---|
/events |
GET | Query captured traffic events |
/stats |
GET | Get event store statistics |
GET /events
| Parameter | Type | Description |
|---|---|---|
direction |
inbound / outbound |
Filter by traffic direction |
kind |
request, response, error, ... |
Filter by event kind |
request_id |
string | Filter by request ID |
limit |
number | Max events to return |
# All events
# Only inbound requests, last 50
# Events for a specific request
GET /stats
Custom Implementations
Custom Transport Listener
use *;
use InboundConnection;
use ;
use async_trait;
let proxy = new
.listener
.upstream_url
.build?;
Custom Converter + Format Detector
use *;
use async_trait;
use Bytes;
;
;
Integration with Brainwires
Use via the brainwires facade crate:
[]
= { = "0.1", = ["proxy"] }
Or use standalone — brainwires-proxy has no dependency on any other Brainwires crate.
License
Licensed under either of Apache License, Version 2.0 or MIT License at your option.