synapse-proxy
A config-driven reverse-proxy sidecar. It forwards incoming requests to upstreams
by longest matching path_prefix, optionally stripping the prefix, injecting
static headers, and streaming the response back.
Configuration
Set SYNAPSE_PROXY_CONFIG_PATH (default synapse-proxy.toml); SYNAPSE_PROXY_ADDR
overrides the listen address.
Upstream HTTP client timeouts (avoid indefinite hangs on broken DNS/MCS paths):
| Variable | Default |
|---|---|
SYNAPSE_PROXY_UPSTREAM_CONNECT_TIMEOUT_SECS |
10 |
SYNAPSE_PROXY_UPSTREAM_TIMEOUT_SECS |
120 |
= "0.0.0.0:8787"
= "127.0.0.1:8788"
= "0.0.0.0:9090"
[]
= { = "BROKER_ORG_ID", = "BROKER_WORKSPACE_ID" }
[[]]
= "cortex"
= "/v1/cortex"
= "http://cortex:8080"
= true
= ["org", "workspace"]
= [
{ = { = "X-Tenant-Id", = "org" } },
{ = { = "X-Workspace-Id", = "workspace" } },
{ = { = "X-User-Id", = "_default" } },
]
[[]]
= "integration-call"
= "/v1/integrations/call"
= "http://integrations:8080/call-workspace"
= true
= ["org", "workspace"]
= [
{ = { = "request", = [
{ = "org", = "org" },
{ = "workspace", = "workspace" },
] } },
]
= [
{ = { = 401, = "auth_expired" } },
{ = { = 404, = "no_connection" } },
]
Context & Transforms
Context sources
The [context] table controls how the per-request context is populated.
static: TOML literal key/value pairs baked in at startup.env: maps a context key to the name of the environment variable read at startup;envvalues win overstaticwhen both are present.- At runtime, the admin endpoint (
/internal/bind) can push a per-overlay that wins over both.DELETE /internal/bindreverts to the static/env baseline.
Route fields
| field | description |
|---|---|
name |
Optional label used in metrics; defaults to path_prefix. |
path_prefix |
Longest-prefix match. |
upstream |
Forwarding target (scheme + host + optional path prefix). |
strip_prefix |
Remove path_prefix from the path before forwarding. |
methods |
Restrict to HTTP methods (e.g. ["POST"]); empty = any. |
require_context |
List of context keys that must be present; returns 503 {"error":"request_failed","detail":"context not bound"} if any are absent. |
request_steps |
Ordered pipeline of transforms applied to the outgoing request. |
response_steps |
Ordered pipeline of transforms applied to the upstream response. |
Built-in transforms
inject — inject a value into a header or dotted body path.
{ inject = { header = "X-Tenant-Id", from_context = "org" } }
{ inject = { header = "X-User-Id", const = "_default" } }
{ inject = { body = "tenant", from_context = "org" } }
Body re-encoding: JSON body transforms (
inject/wraponbody) re-serialize the payload. StaleContent-Length/Transfer-Encodingfrom the client are stripped before forwarding so upstream receives the full JSON (reqwest sets the correct length).
Security note: When a
from_contextkey is absent, header targets are fail-safe: the injector removes any caller-supplied value for that header so it cannot pass through. Body targets rely onrequire_contextas the gate. Anyfrom_contextkey used for identity (e.g. tenant or user headers) SHOULD also be listed in the route'srequire_contextas defense in depth.
wrap — nest the incoming JSON body under a key and inject sibling fields.
{ wrap = { under = "request", inject = [
{ body = "org", from_context = "org" },
{ body = "workspace", from_context = "workspace" },
] } }
error_remap — replace the upstream response body with a normalized error object when a status matches (status code is kept).
{ error_remap = { when_status = 401, error = "auth_expired" } }
{ error_remap = { when_status = 404, error = "no_connection", detail = "resource not found" } }
Custom transforms (extension API)
Register named transforms at startup using ProxyBuilder:
use ;
use ;
use ResolvedContext;
use async_trait;
use Arc;
;
let router = build_router_from_config.unwrap;
Then reference it in TOML:
= [ { = "my-transform" } ]
Listeners
Three listeners are served concurrently:
| listener | config key | default | purpose |
|---|---|---|---|
| Data plane | addr |
0.0.0.0:8787 |
Proxy traffic. |
| Admin | admin_addr |
127.0.0.1:8788 |
Context push/clear. |
| Metrics | metrics_addr |
0.0.0.0:9090 |
Prometheus scrape. |
Endpoints
Data plane
GET /healthz/liveness— always 200.GET /healthz/readiness— 200, or 503 once shutting down (SIGTERM drains in-flight requests).- Everything else — matched against
routesand forwarded;404 {"error":"no_route"}if none match,502 {"error":"request_failed"}if the upstream is unreachable.
Admin (admin_addr)
POST /internal/bind— push a context overlay. Body:{"values":{"org":"acme"},"ttl_seconds":3600}.DELETE /internal/bind— clear the overlay and revert to the startup baseline.
Metrics (metrics_addr)
GET /metrics— Prometheus text format.
Metric names
| name | type | labels | description |
|---|---|---|---|
synapse_proxy_requests_total |
counter | route, method, status, outcome |
Total forwarded requests. |
synapse_proxy_request_duration_seconds |
histogram | route, method |
Request duration. |
synapse_proxy_upstream_errors_total |
counter | route, reason |
Upstream connection/read errors. |
synapse_proxy_transform_errors_total |
counter | route, transform |
Transform pipeline errors. |